From efda3f052c0d4872f35bf54fd4e41d920411e26b Mon Sep 17 00:00:00 2001 From: gucio321 <73652197+gucio321@users.noreply.github.com> Date: Tue, 18 Oct 2022 08:02:50 +0200 Subject: [PATCH 001/167] CMakeList: fix bug when building SDL version for Wayland and using Vulkan failed fix #16163 --- CMakeLists.txt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9084bea3a..25d8d01f2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,14 +184,13 @@ if(UNIX AND NOT (APPLE OR ANDROID) AND VULKAN) endif() # add_definitions(-DVK_USE_PLATFORM_XCB_KHR) - if(USE_WAYLAND_WSI) - find_package(Wayland) - if(NOT WAYLAND_FOUND) - message(STATUS "Could not find Wayland libraries, disabling Wayland WSI support for Vulkan.") - else() - include_directories(${WAYLAND_INCLUDE_DIR}) - add_definitions(-DVK_USE_PLATFORM_WAYLAND_KHR) - endif() + find_package(Wayland) + if(NOT WAYLAND_FOUND) + message(STATUS "Could not find Wayland libraries, disabling Wayland WSI support for Vulkan.") + else() + include_directories(${WAYLAND_INCLUDE_DIR}) + add_definitions(-DVK_USE_PLATFORM_WAYLAND_KHR) + add_definitions(-DUSE_WAYLAND_WSI=ON) endif() if(USE_VULKAN_DISPLAY_KHR) From 2a7d6fe9f8a78e59db3e3e2428c88ed0b24178e7 Mon Sep 17 00:00:00 2001 From: Lubos Date: Fri, 30 Jun 2023 14:38:35 +0200 Subject: [PATCH 002/167] Postshader - Fake reflections added --- UWP/UWP.vcxproj | 3 +++ UWP/UWP.vcxproj.filters | 3 +++ assets/shaders/defaultshaders.ini | 12 ++++++++++ assets/shaders/fakereclections.fsh | 36 ++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 assets/shaders/fakereclections.fsh diff --git a/UWP/UWP.vcxproj b/UWP/UWP.vcxproj index 58dc54ec1b..f3234ced02 100644 --- a/UWP/UWP.vcxproj +++ b/UWP/UWP.vcxproj @@ -835,6 +835,9 @@ true + + true + true diff --git a/UWP/UWP.vcxproj.filters b/UWP/UWP.vcxproj.filters index 5b018c1498..94485cc8b0 100644 --- a/UWP/UWP.vcxproj.filters +++ b/UWP/UWP.vcxproj.filters @@ -307,6 +307,9 @@ Content\shaders + + Content\shaders + Content\shaders diff --git a/assets/shaders/defaultshaders.ini b/assets/shaders/defaultshaders.ini index b7cd268733..39cf42257d 100644 --- a/assets/shaders/defaultshaders.ini +++ b/assets/shaders/defaultshaders.ini @@ -38,6 +38,18 @@ SettingDefaultValue2=1.0 SettingMaxValue2=2.0 SettingMinValue2=0.5 SettingStep2=0.1 +[FakeReclections] +Name=Fake Reflections +Fragment=fakereclections.fsh +Vertex=fxaa.vsh +SettingName1=Amount +SettingDefaultValue1=0.6 +SettingMaxValue1=1.0 +SettingMinValue1=0.0 +SettingName2=Power +SettingDefaultValue2=0.5 +SettingMaxValue2=1.0 +SettingMinValue2=0.0 [Bloom] Name=Bloom Shader Fragment=bloom.fsh diff --git a/assets/shaders/fakereclections.fsh b/assets/shaders/fakereclections.fsh new file mode 100644 index 0000000000..b2302edd92 --- /dev/null +++ b/assets/shaders/fakereclections.fsh @@ -0,0 +1,36 @@ +// Simple fake reflection shader; created to use in PPSSPP. +// Without excessive samples and complex nested loops +// (to make it compatible with low-end GPUs and to ensure ps_2_0 compatibility). + +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D sampler0; +varying vec2 v_texcoord0; + +uniform vec4 u_setting; + +void main() +{ + //get the pixel color + vec3 color = texture2D(sampler0, v_texcoord0.xy).rgb; + float gray = (color.r + color.g + color.b) / 3.0; + float saturation = (abs(color.r - gray) + abs(color.g - gray) + abs(color.b - gray)) / 3.0; + + //persistent random offset to hide that the reflection is wrong + float rndx = mod(v_texcoord0.x + gray, 0.03) + mod(v_texcoord0.y + saturation, 0.05); + float rndy = mod(v_texcoord0.y + saturation, 0.03) + mod(v_texcoord0.x + gray, 0.05); + + //show the effect mainly on the bottom part of the screen + float step = (max(gray, saturation) + 0.1) * v_texcoord0.y; + + //the fake reflection is just a watered copy of the frame moved slightly lower + vec3 reflection = texture2D(sampler0, v_texcoord0 + vec2(rndx, rndy - min(v_texcoord0.y, 0.25)) * step).rgb; + + //apply parameters and mix the colors + reflection *= 4.0 * (1.0 - gray) * u_setting.x; + reflection *= reflection * step * u_setting.y; + gl_FragColor = vec4(color + reflection, 1.0); +} From b8dbe02010ffae82a3a33a9d362d146556924576 Mon Sep 17 00:00:00 2001 From: Nemoumbra Date: Mon, 3 Jul 2023 18:14:26 +0300 Subject: [PATCH 003/167] Added a new window class --- CMakeLists.txt | 2 + Windows/Debugger/BreakpointWindow.cpp | 50 ++++----- Windows/Debugger/ScanRemoveWindow.cpp | 142 ++++++++++++++++++++++++++ Windows/Debugger/ScanRemoveWindow.h | 30 ++++++ Windows/PPSSPP.vcxproj | 2 + Windows/PPSSPP.vcxproj.filters | 6 ++ Windows/resource.h | 6 ++ 7 files changed, 215 insertions(+), 23 deletions(-) create mode 100644 Windows/Debugger/ScanRemoveWindow.cpp create mode 100644 Windows/Debugger/ScanRemoveWindow.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a2e49dd691..a485884da7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2373,6 +2373,8 @@ set(WindowsFiles Windows/Debugger/Debugger_VFPUDlg.h Windows/Debugger/WatchItemWindow.cpp Windows/Debugger/WatchItemWindow.h + Windows/Debugger/ScanRemoveWindow.cpp + Windows/Debugger/ScanRemoveWindow.h Windows/GEDebugger/CtrlDisplayListView.cpp Windows/GEDebugger/SimpleGLWindow.cpp Windows/GEDebugger/TabState.cpp diff --git a/Windows/Debugger/BreakpointWindow.cpp b/Windows/Debugger/BreakpointWindow.cpp index 1a1b1a1d21..d26c5f6c29 100644 --- a/Windows/Debugger/BreakpointWindow.cpp +++ b/Windows/Debugger/BreakpointWindow.cpp @@ -11,7 +11,8 @@ INT_PTR CALLBACK BreakpointWindow::StaticDlgFunc(HWND hWnd, UINT iMsg, WPARAM wP if (iMsg == WM_INITDIALOG) { SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)lParam); thiz = (BreakpointWindow *)lParam; - } else { + } + else { thiz = (BreakpointWindow *)GetWindowLongPtr(hWnd, GWLP_USERDATA); } @@ -40,15 +41,15 @@ INT_PTR BreakpointWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lP EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_ONCHANGE), memory); EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_SIZE), memory); EnableWindow(GetDlgItem(hwnd, IDC_BREAKPOINT_LOG_FORMAT), log); - + if (address != -1) { snprintf(str, sizeof(str), "0x%08X", address); - SetWindowTextA(GetDlgItem(hwnd,IDC_BREAKPOINT_ADDRESS),str); + SetWindowTextA(GetDlgItem(hwnd, IDC_BREAKPOINT_ADDRESS), str); } snprintf(str, sizeof(str), "0x%08X", size); - SetWindowTextA(GetDlgItem(hwnd, IDC_BREAKPOINT_SIZE),str); - + SetWindowTextA(GetDlgItem(hwnd, IDC_BREAKPOINT_SIZE), str); + SetWindowTextW(GetDlgItem(hwnd, IDC_BREAKPOINT_CONDITION), ConvertUTF8ToWString(condition).c_str()); SetWindowTextW(GetDlgItem(hwnd, IDC_BREAKPOINT_LOG_FORMAT), ConvertUTF8ToWString(logFormat).c_str()); return TRUE; @@ -121,7 +122,7 @@ INT_PTR BreakpointWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lP bool BreakpointWindow::fetchDialogData(HWND hwnd) { - char str[256],errorMessage[512]; + char str[256], errorMessage[512]; PostfixExpression exp; memory = GetCheckState(hwnd, IDC_BREAKPOINT_MEMORY); @@ -132,36 +133,36 @@ bool BreakpointWindow::fetchDialogData(HWND hwnd) onChange = GetCheckState(hwnd, IDC_BREAKPOINT_ONCHANGE); // parse address - GetWindowTextA(GetDlgItem(hwnd,IDC_BREAKPOINT_ADDRESS),str,256); - if (cpu->initExpression(str,exp) == false) + GetWindowTextA(GetDlgItem(hwnd, IDC_BREAKPOINT_ADDRESS), str, 256); + if (cpu->initExpression(str, exp) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); - MessageBoxA(hwnd,errorMessage,"Error",MB_OK); + MessageBoxA(hwnd, errorMessage, "Error", MB_OK); return false; } - if (cpu->parseExpression(exp,address) == false) + if (cpu->parseExpression(exp, address) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); - MessageBoxA(hwnd,errorMessage,"Error",MB_OK); + MessageBoxA(hwnd, errorMessage, "Error", MB_OK); return false; } if (memory) { // parse size - GetWindowTextA(GetDlgItem(hwnd,IDC_BREAKPOINT_SIZE),str,256); - if (cpu->initExpression(str,exp) == false) + GetWindowTextA(GetDlgItem(hwnd, IDC_BREAKPOINT_SIZE), str, 256); + if (cpu->initExpression(str, exp) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); - MessageBoxA(hwnd,errorMessage,"Error",MB_OK); + MessageBoxA(hwnd, errorMessage, "Error", MB_OK); return false; } - if (cpu->parseExpression(exp,size) == false) + if (cpu->parseExpression(exp, size) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); - MessageBoxA(hwnd,errorMessage,"Error",MB_OK); + MessageBoxA(hwnd, errorMessage, "Error", MB_OK); return false; } } @@ -176,7 +177,7 @@ bool BreakpointWindow::fetchDialogData(HWND hwnd) if (cpu->initExpression(condition.c_str(), compiledCondition) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", condition.c_str(), getExpressionError()); - MessageBoxA(hwnd,errorMessage,"Error",MB_OK); + MessageBoxA(hwnd, errorMessage, "Error", MB_OK); return false; } } @@ -229,16 +230,17 @@ void BreakpointWindow::addBreakpoint() { } CBreakPoints::ChangeMemCheckLogFormat(address, address + size, logFormat); - } else { + } + else { // add breakpoint - CBreakPoints::AddBreakPoint(address,false); + CBreakPoints::AddBreakPoint(address, false); if (!condition.empty()) { BreakPointCond cond; cond.debug = cpu; cond.expressionString = condition; cond.expression = compiledCondition; - CBreakPoints::ChangeBreakPointAddCond(address,cond); + CBreakPoints::ChangeBreakPointAddCond(address, cond); } CBreakPoints::ChangeBreakPoint(address, result); @@ -257,11 +259,12 @@ void BreakpointWindow::loadFromMemcheck(const MemCheck &memcheck) { enabled = (memcheck.result & BREAK_ACTION_PAUSE) != 0; address = memcheck.start; - size = memcheck.end-address; + size = memcheck.end - address; if (memcheck.hasCondition) { condition = memcheck.condition.expressionString; - } else { + } + else { condition.clear(); } @@ -278,7 +281,8 @@ void BreakpointWindow::loadFromBreakpoint(const BreakPoint& breakpoint) { if (breakpoint.hasCond) { condition = breakpoint.cond.expressionString; - } else { + } + else { condition.clear(); } diff --git a/Windows/Debugger/ScanRemoveWindow.cpp b/Windows/Debugger/ScanRemoveWindow.cpp new file mode 100644 index 0000000000..efe50a1969 --- /dev/null +++ b/Windows/Debugger/ScanRemoveWindow.cpp @@ -0,0 +1,142 @@ + +#include "ScanRemoveWindow.h" +#include "../resource.h" + + + +bool ScanRemoveWindow::GetCheckState(HWND hwnd, int dlgItem) { + return SendMessage(GetDlgItem(hwnd, dlgItem), BM_GETCHECK, 0, 0) != 0; +} + +bool ScanRemoveWindow::fetchDialogData(HWND hwnd) +{ + char str[256], errorMessage[512]; + PostfixExpression exp; + + scan = GetCheckState(hwnd, IDC_SCANREMOVE_SCAN); + + // Parse the address + GetWindowTextA(GetDlgItem(hwnd, IDC_SCANREMOVE_ADDRESS), str, 256); + + if (cpu->initExpression(str, exp) == false) + { + snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); + MessageBoxA(hwnd, errorMessage, "Error", MB_OK); + return false; + } + if (cpu->parseExpression(exp, address) == false) + { + snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); + MessageBoxA(hwnd, errorMessage, "Error", MB_OK); + return false; + } + + // Parse the size + GetWindowTextA(GetDlgItem(hwnd, IDC_SCANREMOVE_SIZE), str, 256); + + if (cpu->initExpression(str, exp) == false) + { + snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); + MessageBoxA(hwnd, errorMessage, "Error", MB_OK); + return false; + } + if (cpu->parseExpression(exp, size) == false) + { + snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); + MessageBoxA(hwnd, errorMessage, "Error", MB_OK); + return false; + } + + return true; +} + +INT_PTR CALLBACK ScanRemoveWindow::StaticDlgFunc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { + ScanRemoveWindow *thiz; + if (iMsg == WM_INITDIALOG) { + SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)lParam); + thiz = (ScanRemoveWindow *)lParam; + } + else { + thiz = (ScanRemoveWindow *)GetWindowLongPtr(hWnd, GWLP_USERDATA); + } + + if (!thiz) + return FALSE; + return thiz->DlgFunc(hWnd, iMsg, wParam, lParam); +} + +INT_PTR ScanRemoveWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) +{ + char str[128]; + + switch (iMsg) + { + case WM_INITDIALOG: + + // Set the radiobutton values + SendMessage(GetDlgItem(hwnd, IDC_SCANREMOVE_SCAN), BM_SETCHECK, scan ? BST_CHECKED : BST_UNCHECKED, 0); + SendMessage(GetDlgItem(hwnd, IDC_SCANREMOVE_REMOVE), BM_SETCHECK, scan ? BST_UNCHECKED : BST_CHECKED, 0); + + // Set the text in the textboxes + if (address != -1) { + snprintf(str, sizeof(str), "0x%08X", address); + SetWindowTextA(GetDlgItem(hwnd, IDC_SCANREMOVE_ADDRESS), str); + } + snprintf(str, sizeof(str), "0x%08X", size); + SetWindowTextA(GetDlgItem(hwnd, IDC_SCANREMOVE_SIZE), str); + + return TRUE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_SCANREMOVE_SCAN: + switch (HIWORD(wParam)) + { + case BN_CLICKED: + scan = true; + break; + } + break; + case IDC_SCANREMOVE_REMOVE: + switch (HIWORD(wParam)) + { + case BN_CLICKED: + scan = false; + break; + } + break; + case IDC_SCANREMOVE_OK: + switch (HIWORD(wParam)) + { + case BN_CLICKED: + if (fetchDialogData(hwnd)) { + EndDialog(hwnd, true); + } + break; + }; + break; + case IDC_SCANREMOVE_CANCEL: + switch (HIWORD(wParam)) + { + case BN_CLICKED: + EndDialog(hwnd, false); + break; + }; + break; + case IDOK: + if (fetchDialogData(hwnd)) { + EndDialog(hwnd, true); + } + break; + case IDCANCEL: + EndDialog(hwnd, false); + break; + } + } + + return FALSE; +} + +bool ScanRemoveWindow::exec() { + return DialogBoxParam(GetModuleHandle(0), MAKEINTRESOURCE(IDD_BREAKPOINT), parentHwnd, StaticDlgFunc, (LPARAM)this) != 0; +} diff --git a/Windows/Debugger/ScanRemoveWindow.h b/Windows/Debugger/ScanRemoveWindow.h new file mode 100644 index 0000000000..753fe95685 --- /dev/null +++ b/Windows/Debugger/ScanRemoveWindow.h @@ -0,0 +1,30 @@ +#pragma once +#include +#include "Common/CommonWindows.h" +#include "Common/CommonTypes.h" +#include "Core/Debugger/DebugInterface.h" + +class ScanRemoveWindow { + HWND parentHwnd; + DebugInterface* cpu; + + bool scan; + u32 address; + u32 size; + + bool GetCheckState(HWND hwnd, int dlgItem); + bool fetchDialogData(HWND hwnd); + + static INT_PTR CALLBACK StaticDlgFunc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam); + INT_PTR DlgFunc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam); + +public: + ScanRemoveWindow(HWND parent, DebugInterface* cpu) : cpu(cpu) { + parentHwnd = parent; + scan = true; + address = -1; + size = 1; + } + + bool exec(); +}; diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index f0a38f80da..a222977c2f 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -844,6 +844,7 @@ + @@ -1400,6 +1401,7 @@ + diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index 35fa64082c..8c7e46cb3a 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -280,6 +280,9 @@ Windows\Debugger + + Windows\Debugger + @@ -568,6 +571,9 @@ Windows\Debugger + + Windows\Debugger + Other Platforms\Mac diff --git a/Windows/resource.h b/Windows/resource.h index ae1ff111c3..82e363b816 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -122,6 +122,12 @@ #define IDC_SHOWOFFSETS 1200 #define IDC_GEDBG_PRIMCOUNTER 1201 #define IDC_BUTTON_SEARCH 1204 +#define IDC_SCANREMOVE_SCAN 1205 +#define IDC_SCANREMOVE_REMOVE 1206 +#define IDC_SCANREMOVE_ADDRESS 1207 +#define IDC_SCANREMOVE_SIZE 1208 +#define IDC_SCANREMOVE_OK 1209 +#define IDC_SCANREMOVE_CANCEL 1210 #define ID_FILE_EXIT 40000 #define ID_DEBUG_SAVEMAPFILE 40001 From abd2b21d60e0133539efa48462d4d6d3107585b7 Mon Sep 17 00:00:00 2001 From: Nemoumbra Date: Mon, 3 Jul 2023 20:33:00 +0300 Subject: [PATCH 004/167] Added the logic to the ScanRemoveWindow --- Windows/Debugger/CtrlDisAsmView.cpp | 8 +++ Windows/Debugger/ScanRemoveWindow.cpp | 76 ++++++++++++++++++++++----- Windows/Debugger/ScanRemoveWindow.h | 21 +++++--- Windows/ppsspp.rc | 16 ++++++ Windows/resource.h | 2 + 5 files changed, 104 insertions(+), 19 deletions(-) diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 89529d06e6..257c54720a 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -18,6 +18,7 @@ #include "Windows/Debugger/Debugger_MemoryDlg.h" #include "Windows/Debugger/DebuggerShared.h" #include "Windows/Debugger/BreakpointWindow.h" +#include "Windows/Debugger/ScanRemoveWindow.h" #include "Windows/main.h" #include "Common/CommonWindows.h" @@ -978,6 +979,13 @@ void CtrlDisAsmView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) NopInstructions(selectRangeStart, selectRangeEnd); redraw(); break; + case ID_DISASM_SCANREMOVE: + { + ScanRemoveWindow srw(wnd, debugger); + if (srw.exec()) { + srw.eval(); + } + } case ID_DISASM_SETPCTOHERE: debugger->setPC(curAddress); redraw(); diff --git a/Windows/Debugger/ScanRemoveWindow.cpp b/Windows/Debugger/ScanRemoveWindow.cpp index efe50a1969..c93fe02adb 100644 --- a/Windows/Debugger/ScanRemoveWindow.cpp +++ b/Windows/Debugger/ScanRemoveWindow.cpp @@ -1,9 +1,7 @@ - #include "ScanRemoveWindow.h" #include "../resource.h" - bool ScanRemoveWindow::GetCheckState(HWND hwnd, int dlgItem) { return SendMessage(GetDlgItem(hwnd, dlgItem), BM_GETCHECK, 0, 0) != 0; } @@ -13,7 +11,7 @@ bool ScanRemoveWindow::fetchDialogData(HWND hwnd) char str[256], errorMessage[512]; PostfixExpression exp; - scan = GetCheckState(hwnd, IDC_SCANREMOVE_SCAN); + scan_ = GetCheckState(hwnd, IDC_SCANREMOVE_SCAN); // Parse the address GetWindowTextA(GetDlgItem(hwnd, IDC_SCANREMOVE_ADDRESS), str, 256); @@ -24,7 +22,7 @@ bool ScanRemoveWindow::fetchDialogData(HWND hwnd) MessageBoxA(hwnd, errorMessage, "Error", MB_OK); return false; } - if (cpu->parseExpression(exp, address) == false) + if (cpu->parseExpression(exp, address_) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); MessageBoxA(hwnd, errorMessage, "Error", MB_OK); @@ -40,13 +38,19 @@ bool ScanRemoveWindow::fetchDialogData(HWND hwnd) MessageBoxA(hwnd, errorMessage, "Error", MB_OK); return false; } - if (cpu->parseExpression(exp, size) == false) + if (cpu->parseExpression(exp, size_) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); MessageBoxA(hwnd, errorMessage, "Error", MB_OK); return false; } + // Now let's validate the range + if (!Memory::IsValidRange(address_, size_)) { + MessageBoxA(hwnd, "Invalid range", "Error", MB_OK); + return false; + } + return true; } @@ -74,15 +78,15 @@ INT_PTR ScanRemoveWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lP case WM_INITDIALOG: // Set the radiobutton values - SendMessage(GetDlgItem(hwnd, IDC_SCANREMOVE_SCAN), BM_SETCHECK, scan ? BST_CHECKED : BST_UNCHECKED, 0); - SendMessage(GetDlgItem(hwnd, IDC_SCANREMOVE_REMOVE), BM_SETCHECK, scan ? BST_UNCHECKED : BST_CHECKED, 0); + SendMessage(GetDlgItem(hwnd, IDC_SCANREMOVE_SCAN), BM_SETCHECK, scan_ ? BST_CHECKED : BST_UNCHECKED, 0); + SendMessage(GetDlgItem(hwnd, IDC_SCANREMOVE_REMOVE), BM_SETCHECK, scan_ ? BST_UNCHECKED : BST_CHECKED, 0); // Set the text in the textboxes - if (address != -1) { - snprintf(str, sizeof(str), "0x%08X", address); + if (address_ != -1) { + snprintf(str, sizeof(str), "0x%08X", address_); SetWindowTextA(GetDlgItem(hwnd, IDC_SCANREMOVE_ADDRESS), str); } - snprintf(str, sizeof(str), "0x%08X", size); + snprintf(str, sizeof(str), "0x%08X", size_); SetWindowTextA(GetDlgItem(hwnd, IDC_SCANREMOVE_SIZE), str); return TRUE; @@ -93,7 +97,7 @@ INT_PTR ScanRemoveWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lP switch (HIWORD(wParam)) { case BN_CLICKED: - scan = true; + scan_ = true; break; } break; @@ -101,7 +105,7 @@ INT_PTR ScanRemoveWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lP switch (HIWORD(wParam)) { case BN_CLICKED: - scan = false; + scan_ = false; break; } break; @@ -138,5 +142,51 @@ INT_PTR ScanRemoveWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lP } bool ScanRemoveWindow::exec() { - return DialogBoxParam(GetModuleHandle(0), MAKEINTRESOURCE(IDD_BREAKPOINT), parentHwnd, StaticDlgFunc, (LPARAM)this) != 0; + return DialogBoxParam(GetModuleHandle(0), MAKEINTRESOURCE(IDD_SCANREMOVE), parentHwnd, StaticDlgFunc, (LPARAM)this) != 0; +} + +void ScanRemoveWindow::Scan() { + bool insertSymbols = MIPSAnalyst::ScanForFunctions(address_, address_ + size_ - 1, true); + MIPSAnalyst::FinalizeScan(insertSymbols); +} + +void ScanRemoveWindow::Remove() { + u32 func_address = g_symbolMap->GetFunctionStart(address_); + if (func_address == SymbolMap::INVALID_ADDRESS) { + func_address = g_symbolMap->GetNextSymbolAddress(address_, SymbolType::ST_FUNCTION); + } + + u32 counter = 0; + while (func_address < address_ + size_ && func_address != SymbolMap::INVALID_ADDRESS) { + g_symbolMap->RemoveFunction(func_address, true); + ++counter; + func_address = g_symbolMap->GetNextSymbolAddress(address_, SymbolType::ST_FUNCTION); + } + + if (counter) { + MIPSAnalyst::ForgetFunctions(address_, address_ + size_ - 1); + + // The following was copied from hle.func.remove: + g_symbolMap->SortSymbols(); + + MIPSAnalyst::UpdateHashMap(); + MIPSAnalyst::ApplyHashMap(); + + if (g_Config.bFuncReplacements) { + MIPSAnalyst::ReplaceFunctions(); + } + + // Clear cache for branch lines and such. + DisassemblyManager manager; + manager.clear(); + } +} + +void ScanRemoveWindow::eval() { + if (scan_) { + Scan(); + } + else { + Remove(); + } } diff --git a/Windows/Debugger/ScanRemoveWindow.h b/Windows/Debugger/ScanRemoveWindow.h index 753fe95685..e4d8d45b6f 100644 --- a/Windows/Debugger/ScanRemoveWindow.h +++ b/Windows/Debugger/ScanRemoveWindow.h @@ -2,15 +2,23 @@ #include #include "Common/CommonWindows.h" #include "Common/CommonTypes.h" +#include "Core/Config.h" #include "Core/Debugger/DebugInterface.h" +#include "Core/Debugger/SymbolMap.h" +#include "Core/MIPS/MIPSAnalyst.h" +#include "Core/Debugger/DisassemblyManager.h" +#include "Core/MemMap.h" class ScanRemoveWindow { HWND parentHwnd; DebugInterface* cpu; - bool scan; - u32 address; - u32 size; + bool scan_; + u32 address_; + u32 size_; + + void Scan(); + void Remove(); bool GetCheckState(HWND hwnd, int dlgItem); bool fetchDialogData(HWND hwnd); @@ -21,10 +29,11 @@ class ScanRemoveWindow { public: ScanRemoveWindow(HWND parent, DebugInterface* cpu) : cpu(cpu) { parentHwnd = parent; - scan = true; - address = -1; - size = 1; + scan_ = true; + address_ = -1; + size_ = 1; } bool exec(); + void eval(); }; diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index 85e33bee8c..fd7edc458e 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -380,6 +380,21 @@ BEGIN PUSHBUTTON "Cancel",IDC_BREAKPOINT_CANCEL,186,98,42,14 END +IDD_SCANREMOVE DIALOGEX 0, 0, 236, 119 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Edit" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Address",IDC_STATIC,7,8,27,8 + EDITTEXT IDC_SCANREMOVE_ADDRESS,41,7,69,14,ES_AUTOHSCROLL + CONTROL "Scan",IDC_SCANREMOVE_SCAN,"Button",BS_AUTORADIOBUTTON,123,8,36,10 + CONTROL "Remove",IDC_SCANREMOVE_REMOVE,"Button",BS_AUTORADIOBUTTON,170,8,36,10 + LTEXT "Size",IDC_STATIC,7,27,14,8 + EDITTEXT IDC_SCANREMOVE_SIZE,41,25,69,14,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDC_SCANREMOVE_OK,144,98,41,14 + PUSHBUTTON "Cancel",IDC_SCANREMOVE_CANCEL,186,98,42,14 +END + IDD_DUMPMEMORY DIALOGEX 0, 0, 230, 100 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Dump memory" @@ -755,6 +770,7 @@ BEGIN MENUITEM "Remove Function", ID_DISASM_REMOVEFUNCTION MENUITEM "Add Function Here", ID_DISASM_ADDFUNCTION MENUITEM "NOP instruction(s)", ID_DISASM_NOPINSTRUCTION + MENUITEM "Scan / remove functions", ID_DISASM_SCANREMOVE END POPUP "reglist" BEGIN diff --git a/Windows/resource.h b/Windows/resource.h index 82e363b816..a38c518527 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -61,6 +61,7 @@ #define IDD_GEDBG_TAB_MATRICES 255 #define IDD_GEDBG_STEPCOUNT 256 #define IDD_CPUWATCH 257 +#define IDD_SCANREMOVE 258 #define IDC_STOPGO 1001 #define IDC_ADDRESS 1002 @@ -352,6 +353,7 @@ #define IDC_DISASM_FMT_INT 40233 #define IDC_DISASM_FMT_FLOAT 40234 #define IDC_DISASM_FMT_STR 40235 +#define ID_DISASM_SCANREMOVE 40236 // Dummy option to let the buffered rendering hotkey cycle through all the options. From 23cbddd3c65e770e41d8182047f0ececb1f044ad Mon Sep 17 00:00:00 2001 From: Nemoumbra Date: Mon, 3 Jul 2023 22:35:47 +0300 Subject: [PATCH 005/167] Logical error fixed and func list properly updated now --- Windows/Debugger/CtrlDisAsmView.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 257c54720a..89ac9507bb 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -984,8 +984,11 @@ void CtrlDisAsmView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) ScanRemoveWindow srw(wnd, debugger); if (srw.exec()) { srw.eval(); + SendMessage(GetParent(wnd), WM_DEB_MAPLOADED, 0, 0); + redraw(); } } + break; case ID_DISASM_SETPCTOHERE: debugger->setPC(curAddress); redraw(); From cdba944643b6813749b05dcb747ae8d68a437d65 Mon Sep 17 00:00:00 2001 From: Nemoumbra Date: Tue, 4 Jul 2023 18:15:24 +0300 Subject: [PATCH 006/167] Updated the resource file --- Windows/ppsspp.rc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index fd7edc458e..9a373c6520 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -380,19 +380,19 @@ BEGIN PUSHBUTTON "Cancel",IDC_BREAKPOINT_CANCEL,186,98,42,14 END -IDD_SCANREMOVE DIALOGEX 0, 0, 236, 119 +IDD_SCANREMOVE DIALOGEX 0, 0, 236, 72 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Edit" +CAPTION "Edit functions" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN LTEXT "Address",IDC_STATIC,7,8,27,8 EDITTEXT IDC_SCANREMOVE_ADDRESS,41,7,69,14,ES_AUTOHSCROLL - CONTROL "Scan",IDC_SCANREMOVE_SCAN,"Button",BS_AUTORADIOBUTTON,123,8,36,10 - CONTROL "Remove",IDC_SCANREMOVE_REMOVE,"Button",BS_AUTORADIOBUTTON,170,8,36,10 + CONTROL "Scan in range",IDC_SCANREMOVE_SCAN,"Button",BS_AUTORADIOBUTTON,144,8,70,10 + CONTROL "Remove in range",IDC_SCANREMOVE_REMOVE,"Button",BS_AUTORADIOBUTTON,144,27,70,10 LTEXT "Size",IDC_STATIC,7,27,14,8 EDITTEXT IDC_SCANREMOVE_SIZE,41,25,69,14,ES_AUTOHSCROLL - DEFPUSHBUTTON "OK",IDC_SCANREMOVE_OK,144,98,41,14 - PUSHBUTTON "Cancel",IDC_SCANREMOVE_CANCEL,186,98,42,14 + DEFPUSHBUTTON "OK",IDC_SCANREMOVE_OK,144,51,41,14 + PUSHBUTTON "Cancel",IDC_SCANREMOVE_CANCEL,186,51,42,14 END IDD_DUMPMEMORY DIALOGEX 0, 0, 230, 100 @@ -770,7 +770,7 @@ BEGIN MENUITEM "Remove Function", ID_DISASM_REMOVEFUNCTION MENUITEM "Add Function Here", ID_DISASM_ADDFUNCTION MENUITEM "NOP instruction(s)", ID_DISASM_NOPINSTRUCTION - MENUITEM "Scan / remove functions", ID_DISASM_SCANREMOVE + MENUITEM "Scan / remove functions...", ID_DISASM_SCANREMOVE END POPUP "reglist" BEGIN From 5ca099a747157763d09de79e071442ed066ac007 Mon Sep 17 00:00:00 2001 From: Nemoumbra Date: Mon, 17 Jul 2023 16:38:24 +0300 Subject: [PATCH 007/167] Scan/remove -> edit symbols --- CMakeLists.txt | 4 +- Windows/Debugger/CtrlDisAsmView.cpp | 10 ++-- ...RemoveWindow.cpp => EditSymbolsWindow.cpp} | 48 +++++++++---------- ...ScanRemoveWindow.h => EditSymbolsWindow.h} | 4 +- Windows/PPSSPP.vcxproj | 4 +- Windows/PPSSPP.vcxproj.filters | 4 +- Windows/ppsspp.rc | 18 +++---- Windows/resource.h | 16 +++---- 8 files changed, 54 insertions(+), 54 deletions(-) rename Windows/Debugger/{ScanRemoveWindow.cpp => EditSymbolsWindow.cpp} (71%) rename Windows/Debugger/{ScanRemoveWindow.h => EditSymbolsWindow.h} (89%) diff --git a/CMakeLists.txt b/CMakeLists.txt index a485884da7..dddcf33403 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2373,8 +2373,8 @@ set(WindowsFiles Windows/Debugger/Debugger_VFPUDlg.h Windows/Debugger/WatchItemWindow.cpp Windows/Debugger/WatchItemWindow.h - Windows/Debugger/ScanRemoveWindow.cpp - Windows/Debugger/ScanRemoveWindow.h + Windows/Debugger/EditSymbolsWindow.cpp + Windows/Debugger/EditSymbolsWindow.h Windows/GEDebugger/CtrlDisplayListView.cpp Windows/GEDebugger/SimpleGLWindow.cpp Windows/GEDebugger/TabState.cpp diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 89ac9507bb..946a131a1c 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -18,7 +18,7 @@ #include "Windows/Debugger/Debugger_MemoryDlg.h" #include "Windows/Debugger/DebuggerShared.h" #include "Windows/Debugger/BreakpointWindow.h" -#include "Windows/Debugger/ScanRemoveWindow.h" +#include "Windows/Debugger/EditSymbolsWindow.h" #include "Windows/main.h" #include "Common/CommonWindows.h" @@ -979,11 +979,11 @@ void CtrlDisAsmView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) NopInstructions(selectRangeStart, selectRangeEnd); redraw(); break; - case ID_DISASM_SCANREMOVE: + case ID_DISASM_EDITSYMBOLS: { - ScanRemoveWindow srw(wnd, debugger); - if (srw.exec()) { - srw.eval(); + EditSymbolsWindow esw(wnd, debugger); + if (esw.exec()) { + esw.eval(); SendMessage(GetParent(wnd), WM_DEB_MAPLOADED, 0, 0); redraw(); } diff --git a/Windows/Debugger/ScanRemoveWindow.cpp b/Windows/Debugger/EditSymbolsWindow.cpp similarity index 71% rename from Windows/Debugger/ScanRemoveWindow.cpp rename to Windows/Debugger/EditSymbolsWindow.cpp index c93fe02adb..607b67e3d5 100644 --- a/Windows/Debugger/ScanRemoveWindow.cpp +++ b/Windows/Debugger/EditSymbolsWindow.cpp @@ -1,20 +1,20 @@ -#include "ScanRemoveWindow.h" +#include "EditSymbolsWindow.h" #include "../resource.h" -bool ScanRemoveWindow::GetCheckState(HWND hwnd, int dlgItem) { +bool EditSymbolsWindow::GetCheckState(HWND hwnd, int dlgItem) { return SendMessage(GetDlgItem(hwnd, dlgItem), BM_GETCHECK, 0, 0) != 0; } -bool ScanRemoveWindow::fetchDialogData(HWND hwnd) +bool EditSymbolsWindow::fetchDialogData(HWND hwnd) { char str[256], errorMessage[512]; PostfixExpression exp; - scan_ = GetCheckState(hwnd, IDC_SCANREMOVE_SCAN); + scan_ = GetCheckState(hwnd, IDC_EDITSYMBOLS_SCAN); // Parse the address - GetWindowTextA(GetDlgItem(hwnd, IDC_SCANREMOVE_ADDRESS), str, 256); + GetWindowTextA(GetDlgItem(hwnd, IDC_EDITSYMBOLS_ADDRESS), str, 256); if (cpu->initExpression(str, exp) == false) { @@ -30,7 +30,7 @@ bool ScanRemoveWindow::fetchDialogData(HWND hwnd) } // Parse the size - GetWindowTextA(GetDlgItem(hwnd, IDC_SCANREMOVE_SIZE), str, 256); + GetWindowTextA(GetDlgItem(hwnd, IDC_EDITSYMBOLS_SIZE), str, 256); if (cpu->initExpression(str, exp) == false) { @@ -54,14 +54,14 @@ bool ScanRemoveWindow::fetchDialogData(HWND hwnd) return true; } -INT_PTR CALLBACK ScanRemoveWindow::StaticDlgFunc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { - ScanRemoveWindow *thiz; +INT_PTR CALLBACK EditSymbolsWindow::StaticDlgFunc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { + EditSymbolsWindow *thiz; if (iMsg == WM_INITDIALOG) { SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)lParam); - thiz = (ScanRemoveWindow *)lParam; + thiz = (EditSymbolsWindow *)lParam; } else { - thiz = (ScanRemoveWindow *)GetWindowLongPtr(hWnd, GWLP_USERDATA); + thiz = (EditSymbolsWindow *)GetWindowLongPtr(hWnd, GWLP_USERDATA); } if (!thiz) @@ -69,7 +69,7 @@ INT_PTR CALLBACK ScanRemoveWindow::StaticDlgFunc(HWND hWnd, UINT iMsg, WPARAM wP return thiz->DlgFunc(hWnd, iMsg, wParam, lParam); } -INT_PTR ScanRemoveWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) +INT_PTR EditSymbolsWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { char str[128]; @@ -78,22 +78,22 @@ INT_PTR ScanRemoveWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lP case WM_INITDIALOG: // Set the radiobutton values - SendMessage(GetDlgItem(hwnd, IDC_SCANREMOVE_SCAN), BM_SETCHECK, scan_ ? BST_CHECKED : BST_UNCHECKED, 0); - SendMessage(GetDlgItem(hwnd, IDC_SCANREMOVE_REMOVE), BM_SETCHECK, scan_ ? BST_UNCHECKED : BST_CHECKED, 0); + SendMessage(GetDlgItem(hwnd, IDC_EDITSYMBOLS_SCAN), BM_SETCHECK, scan_ ? BST_CHECKED : BST_UNCHECKED, 0); + SendMessage(GetDlgItem(hwnd, IDC_EDITSYMBOLS_REMOVE), BM_SETCHECK, scan_ ? BST_UNCHECKED : BST_CHECKED, 0); // Set the text in the textboxes if (address_ != -1) { snprintf(str, sizeof(str), "0x%08X", address_); - SetWindowTextA(GetDlgItem(hwnd, IDC_SCANREMOVE_ADDRESS), str); + SetWindowTextA(GetDlgItem(hwnd, IDC_EDITSYMBOLS_ADDRESS), str); } snprintf(str, sizeof(str), "0x%08X", size_); - SetWindowTextA(GetDlgItem(hwnd, IDC_SCANREMOVE_SIZE), str); + SetWindowTextA(GetDlgItem(hwnd, IDC_EDITSYMBOLS_SIZE), str); return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { - case IDC_SCANREMOVE_SCAN: + case IDC_EDITSYMBOLS_SCAN: switch (HIWORD(wParam)) { case BN_CLICKED: @@ -101,7 +101,7 @@ INT_PTR ScanRemoveWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lP break; } break; - case IDC_SCANREMOVE_REMOVE: + case IDC_EDITSYMBOLS_REMOVE: switch (HIWORD(wParam)) { case BN_CLICKED: @@ -109,7 +109,7 @@ INT_PTR ScanRemoveWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lP break; } break; - case IDC_SCANREMOVE_OK: + case IDC_EDITSYMBOLS_OK: switch (HIWORD(wParam)) { case BN_CLICKED: @@ -119,7 +119,7 @@ INT_PTR ScanRemoveWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lP break; }; break; - case IDC_SCANREMOVE_CANCEL: + case IDC_EDITSYMBOLS_CANCEL: switch (HIWORD(wParam)) { case BN_CLICKED: @@ -141,16 +141,16 @@ INT_PTR ScanRemoveWindow::DlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lP return FALSE; } -bool ScanRemoveWindow::exec() { - return DialogBoxParam(GetModuleHandle(0), MAKEINTRESOURCE(IDD_SCANREMOVE), parentHwnd, StaticDlgFunc, (LPARAM)this) != 0; +bool EditSymbolsWindow::exec() { + return DialogBoxParam(GetModuleHandle(0), MAKEINTRESOURCE(IDD_EDITSYMBOLS), parentHwnd, StaticDlgFunc, (LPARAM)this) != 0; } -void ScanRemoveWindow::Scan() { +void EditSymbolsWindow::Scan() { bool insertSymbols = MIPSAnalyst::ScanForFunctions(address_, address_ + size_ - 1, true); MIPSAnalyst::FinalizeScan(insertSymbols); } -void ScanRemoveWindow::Remove() { +void EditSymbolsWindow::Remove() { u32 func_address = g_symbolMap->GetFunctionStart(address_); if (func_address == SymbolMap::INVALID_ADDRESS) { func_address = g_symbolMap->GetNextSymbolAddress(address_, SymbolType::ST_FUNCTION); @@ -182,7 +182,7 @@ void ScanRemoveWindow::Remove() { } } -void ScanRemoveWindow::eval() { +void EditSymbolsWindow::eval() { if (scan_) { Scan(); } diff --git a/Windows/Debugger/ScanRemoveWindow.h b/Windows/Debugger/EditSymbolsWindow.h similarity index 89% rename from Windows/Debugger/ScanRemoveWindow.h rename to Windows/Debugger/EditSymbolsWindow.h index e4d8d45b6f..cc5baa9757 100644 --- a/Windows/Debugger/ScanRemoveWindow.h +++ b/Windows/Debugger/EditSymbolsWindow.h @@ -9,7 +9,7 @@ #include "Core/Debugger/DisassemblyManager.h" #include "Core/MemMap.h" -class ScanRemoveWindow { +class EditSymbolsWindow { HWND parentHwnd; DebugInterface* cpu; @@ -27,7 +27,7 @@ class ScanRemoveWindow { INT_PTR DlgFunc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam); public: - ScanRemoveWindow(HWND parent, DebugInterface* cpu) : cpu(cpu) { + EditSymbolsWindow(HWND parent, DebugInterface* cpu) : cpu(cpu) { parentHwnd = parent; scan_ = true; address_ = -1; diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index a222977c2f..f345af5f1a 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -844,7 +844,7 @@ - + @@ -1401,7 +1401,7 @@ - + diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index 8c7e46cb3a..1c228494e9 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -280,7 +280,7 @@ Windows\Debugger - + Windows\Debugger @@ -571,7 +571,7 @@ Windows\Debugger - + Windows\Debugger diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index 9a373c6520..e17d92bdde 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -380,19 +380,19 @@ BEGIN PUSHBUTTON "Cancel",IDC_BREAKPOINT_CANCEL,186,98,42,14 END -IDD_SCANREMOVE DIALOGEX 0, 0, 236, 72 +IDD_EDITSYMBOLS DIALOGEX 0, 0, 236, 72 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Edit functions" +CAPTION "Edit symbols" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN LTEXT "Address",IDC_STATIC,7,8,27,8 - EDITTEXT IDC_SCANREMOVE_ADDRESS,41,7,69,14,ES_AUTOHSCROLL - CONTROL "Scan in range",IDC_SCANREMOVE_SCAN,"Button",BS_AUTORADIOBUTTON,144,8,70,10 - CONTROL "Remove in range",IDC_SCANREMOVE_REMOVE,"Button",BS_AUTORADIOBUTTON,144,27,70,10 + EDITTEXT IDC_EDITSYMBOLS_ADDRESS,41,7,69,14,ES_AUTOHSCROLL + CONTROL "Scan in range",IDC_EDITSYMBOLS_SCAN,"Button",BS_AUTORADIOBUTTON,144,8,70,10 + CONTROL "Remove in range",IDC_EDITSYMBOLS_REMOVE,"Button",BS_AUTORADIOBUTTON,144,27,70,10 LTEXT "Size",IDC_STATIC,7,27,14,8 - EDITTEXT IDC_SCANREMOVE_SIZE,41,25,69,14,ES_AUTOHSCROLL - DEFPUSHBUTTON "OK",IDC_SCANREMOVE_OK,144,51,41,14 - PUSHBUTTON "Cancel",IDC_SCANREMOVE_CANCEL,186,51,42,14 + EDITTEXT IDC_EDITSYMBOLS_SIZE,41,25,69,14,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDC_EDITSYMBOLS_OK,144,51,41,14 + PUSHBUTTON "Cancel",IDC_EDITSYMBOLS_CANCEL,186,51,42,14 END IDD_DUMPMEMORY DIALOGEX 0, 0, 230, 100 @@ -770,7 +770,7 @@ BEGIN MENUITEM "Remove Function", ID_DISASM_REMOVEFUNCTION MENUITEM "Add Function Here", ID_DISASM_ADDFUNCTION MENUITEM "NOP instruction(s)", ID_DISASM_NOPINSTRUCTION - MENUITEM "Scan / remove functions...", ID_DISASM_SCANREMOVE + MENUITEM "Edit symbols...", ID_DISASM_EDITSYMBOLS END POPUP "reglist" BEGIN diff --git a/Windows/resource.h b/Windows/resource.h index a38c518527..3eb5473ac1 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -61,7 +61,7 @@ #define IDD_GEDBG_TAB_MATRICES 255 #define IDD_GEDBG_STEPCOUNT 256 #define IDD_CPUWATCH 257 -#define IDD_SCANREMOVE 258 +#define IDD_EDITSYMBOLS 258 #define IDC_STOPGO 1001 #define IDC_ADDRESS 1002 @@ -123,12 +123,12 @@ #define IDC_SHOWOFFSETS 1200 #define IDC_GEDBG_PRIMCOUNTER 1201 #define IDC_BUTTON_SEARCH 1204 -#define IDC_SCANREMOVE_SCAN 1205 -#define IDC_SCANREMOVE_REMOVE 1206 -#define IDC_SCANREMOVE_ADDRESS 1207 -#define IDC_SCANREMOVE_SIZE 1208 -#define IDC_SCANREMOVE_OK 1209 -#define IDC_SCANREMOVE_CANCEL 1210 +#define IDC_EDITSYMBOLS_SCAN 1205 +#define IDC_EDITSYMBOLS_REMOVE 1206 +#define IDC_EDITSYMBOLS_ADDRESS 1207 +#define IDC_EDITSYMBOLS_SIZE 1208 +#define IDC_EDITSYMBOLS_OK 1209 +#define IDC_EDITSYMBOLS_CANCEL 1210 #define ID_FILE_EXIT 40000 #define ID_DEBUG_SAVEMAPFILE 40001 @@ -353,7 +353,7 @@ #define IDC_DISASM_FMT_INT 40233 #define IDC_DISASM_FMT_FLOAT 40234 #define IDC_DISASM_FMT_STR 40235 -#define ID_DISASM_SCANREMOVE 40236 +#define ID_DISASM_EDITSYMBOLS 40236 // Dummy option to let the buffered rendering hotkey cycle through all the options. From ecea3844b0b654d50b09943efa85a8fb16e48850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 18 Jul 2023 15:13:44 +0200 Subject: [PATCH 008/167] Improved progress bar popups for downloads Now shows the filename, and also there's a delay mode where they'll only be visible if the download takes more than a second, plus they can be named. --- Common/File/PathBrowser.cpp | 2 +- Common/Net/HTTPClient.cpp | 95 +++++++++++++++++-------------- Common/Net/HTTPClient.h | 75 +++++++++++------------- Common/Net/NetBuffer.cpp | 16 +++--- Common/Net/NetBuffer.h | 23 +++++++- Common/System/OSD.cpp | 19 +++++-- Common/System/OSD.h | 11 ++-- Core/Config.cpp | 3 +- Core/FileLoaders/HTTPFileLoader.h | 2 +- Core/HLE/sceNp2.cpp | 2 +- Core/Reporting.cpp | 2 +- Core/RetroAchievements.cpp | 10 ++-- Core/Util/GameManager.cpp | 2 +- Core/WebServer.cpp | 2 +- UI/DevScreens.cpp | 16 ++++-- UI/OnScreenDisplay.cpp | 33 ++--------- UI/RemoteISOScreen.cpp | 4 +- UI/Store.cpp | 7 +-- 18 files changed, 167 insertions(+), 157 deletions(-) diff --git a/Common/File/PathBrowser.cpp b/Common/File/PathBrowser.cpp index 36e03dca5c..4aba1b41e4 100644 --- a/Common/File/PathBrowser.cpp +++ b/Common/File/PathBrowser.cpp @@ -38,7 +38,7 @@ bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *can http::RequestParams req(baseURL.Resource(), "text/plain, text/html; q=0.9, */*; q=0.8"); if (http.Resolve(baseURL.Host().c_str(), baseURL.Port())) { if (http.Connect(2, 20.0, cancel)) { - http::RequestProgress progress(cancel); + net::RequestProgress progress(cancel); code = http.GET(req, &result, responseHeaders, &progress); http.Disconnect(); } diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index 74d3784ab7..98c70db433 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -2,6 +2,7 @@ #include "Common/TimeUtil.h" #include "Common/StringUtils.h" +#include "Common/System/OSD.h" #ifndef _WIN32 #include @@ -217,7 +218,7 @@ bool GetHeaderValue(const std::vector &responseHeaders, const std:: return found; } -void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength, float *progress) { +void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength) { int dechunkedBytes = 0; while (true) { std::string line; @@ -236,14 +237,11 @@ void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength, float *prog return; } dechunkedBytes += chunkSize; - if (progress && contentLength) { - *progress = (float)dechunkedBytes / contentLength; - } inbuffer->Skip(2); } } -int Client::GET(const RequestParams &req, Buffer *output, std::vector &responseHeaders, RequestProgress *progress) { +int Client::GET(const RequestParams &req, Buffer *output, std::vector &responseHeaders, net::RequestProgress *progress) { const char *otherHeaders = "Accept-Encoding: gzip\r\n"; int err = SendRequest("GET", req, otherHeaders, progress); @@ -264,13 +262,13 @@ int Client::GET(const RequestParams &req, Buffer *output, std::vector responseHeaders; int code = GET(req, output, responseHeaders, progress); return code; } -int Client::POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, RequestProgress *progress) { +int Client::POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, net::RequestProgress *progress) { char otherHeaders[2048]; if (mime.empty()) { snprintf(otherHeaders, sizeof(otherHeaders), "Content-Length: %lld\r\n", (long long)data.size()); @@ -296,16 +294,16 @@ int Client::POST(const RequestParams &req, const std::string &data, const std::s return code; } -int Client::POST(const RequestParams &req, const std::string &data, Buffer *output, RequestProgress *progress) { +int Client::POST(const RequestParams &req, const std::string &data, Buffer *output, net::RequestProgress *progress) { return POST(req, data, "", output, progress); } -int Client::SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, RequestProgress *progress) { +int Client::SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, net::RequestProgress *progress) { return SendRequestWithData(method, req, "", otherHeaders, progress); } -int Client::SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, RequestProgress *progress) { - progress->progress = 0.01f; +int Client::SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, net::RequestProgress *progress) { + progress->Update(0.01f); net::Buffer buffer; const char *tpl = @@ -331,7 +329,7 @@ int Client::SendRequestWithData(const char *method, const RequestParams &req, co return 0; } -int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector &responseHeaders, RequestProgress *progress) { +int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector &responseHeaders, net::RequestProgress *progress) { // Snarf all the data we can into RAM. A little unsafe but hey. static constexpr float CANCEL_INTERVAL = 0.25f; bool ready = false; @@ -384,7 +382,7 @@ int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector & return code; } -int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector &responseHeaders, Buffer *output, RequestProgress *progress) { +int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector &responseHeaders, Buffer *output, net::RequestProgress *progress) { bool gzip = false; bool chunked = false; int contentLength = 0; @@ -418,24 +416,19 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vectorprogress = 0.1f; + // Set progress to 2% so it looks like something is happening... + // The later progress updates won't include a reliable percentage. + progress->Update(0.02f); } - if (!contentLength) { - // No way to know how far along we are. Let's just not update the progress counter. - if (!readbuf->ReadAllWithProgress(sock(), contentLength, nullptr, &progress->kBps, progress->cancelled)) - return -1; - } else { - // Let's read in chunks, updating progress between each. - if (!readbuf->ReadAllWithProgress(sock(), contentLength, &progress->progress, &progress->kBps, progress->cancelled)) - return -1; - } + if (!readbuf->ReadAllWithProgress(sock(), contentLength, progress)) + return -1; // output now contains the rest of the reply. Dechunk it. if (!output->IsVoid()) { if (chunked) { - DeChunk(readbuf, output, contentLength, &progress->progress); + DeChunk(readbuf, output, contentLength); + progress->Update(1.0f); } else { output->Append(*readbuf); } @@ -447,19 +440,39 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vectorprogress = 0.0f; + progress->Update(0.0f); // TJDO: is this right? return -1; } output->Append(decompressed); } } - progress->progress = 1.0f; + progress->Update(1.0f); return 0; } -Download::Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile) - : method_(method), progress_(&cancelled_), url_(url), postData_(postData), postMime_(postMime), outfile_(outfile) { +Download::Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode) + : method_(method), progress_(&cancelled_), url_(url), postData_(postData), postMime_(postMime), outfile_(outfile), progressBarMode_(progressBarMode) { + + progress_.callback = [=](float progress) { + std::string message; + if (!name_.empty()) { + message = name_; + } else { + std::size_t pos = url.rfind('/'); + if (pos != std::string::npos) { + message = url.substr(pos + 1); + } else { + message = url; + } + } + if (progressBarMode_ != ProgressBarMode::NONE) { + g_OSD.SetProgressBar(url, std::move(message), 0.0f, 1.0f, progress, progressBarMode_ == ProgressBarMode::DELAYED ? 1.0f : 0.0f); // delay 0.5 seconds before showing. + if (progress == 1.0f) { + g_OSD.RemoveProgressBar(url, Failed() ? false : true, 0.5f); + } + } + }; } Download::~Download() { @@ -480,7 +493,7 @@ void Download::Join() { void Download::SetFailed(int code) { failed_ = true; - progress_.progress = 1.0f; + progress_.Update(1.0f); completed_ = true; } @@ -575,15 +588,16 @@ void Download::Do() { resultCode_ = resultCode; } - progress_.progress = 1.0f; + progress_.Update(1.0f); // Set this last to ensure no race conditions when checking Done. Users must always check // Done before looking at the result code. completed_ = true; } -std::shared_ptr Downloader::StartDownload(const std::string &url, const Path &outfile, const char *acceptMime) { - std::shared_ptr dl(new Download(RequestMethod::GET, url, "", "", outfile)); +std::shared_ptr Downloader::StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime) { + std::shared_ptr dl(new Download(RequestMethod::GET, url, "", "", outfile, mode)); + if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); if (acceptMime) @@ -596,9 +610,10 @@ std::shared_ptr Downloader::StartDownload(const std::string &url, cons std::shared_ptr Downloader::StartDownloadWithCallback( const std::string &url, const Path &outfile, + ProgressBarMode mode, std::function callback, const char *acceptMime) { - std::shared_ptr dl(new Download(RequestMethod::GET, url, "", "", outfile)); + std::shared_ptr dl(new Download(RequestMethod::GET, url, "", "", outfile, mode)); if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); if (acceptMime) @@ -613,8 +628,9 @@ std::shared_ptr Downloader::AsyncPostWithCallback( const std::string &url, const std::string &postData, const std::string &postMime, + ProgressBarMode mode, std::function callback) { - std::shared_ptr dl(new Download(RequestMethod::POST, url, postData, postMime, Path())); + std::shared_ptr dl(new Download(RequestMethod::POST, url, postData, postMime, Path(), mode)); if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); dl->SetCallback(callback); @@ -649,15 +665,6 @@ void Downloader::WaitForAll() { } } -std::vector Downloader::GetCurrentProgress() { - std::vector progress; - for (size_t i = 0; i < downloads_.size(); i++) { - if (!downloads_[i]->IsHidden()) - progress.push_back(downloads_[i]->Progress()); - } - return progress; -} - void Downloader::CancelAll() { for (size_t i = 0; i < downloads_.size(); i++) { downloads_[i]->Cancel(); diff --git a/Common/Net/HTTPClient.h b/Common/Net/HTTPClient.h index 245eec0ee8..8987e3331e 100644 --- a/Common/Net/HTTPClient.h +++ b/Common/Net/HTTPClient.h @@ -44,16 +44,8 @@ namespace http { bool GetHeaderValue(const std::vector &responseHeaders, const std::string &header, std::string *value); -struct RequestProgress { - RequestProgress() {} - explicit RequestProgress(bool *c) : cancelled(c) {} - - float progress = 0.0f; - float kBps = 0.0f; - bool *cancelled = nullptr; -}; - -struct RequestParams { +class RequestParams { +public: RequestParams() {} explicit RequestParams(const char *r) : resource(r) {} RequestParams(const std::string &r, const char *a) : resource(r), acceptMime(a) {} @@ -68,20 +60,20 @@ public: ~Client(); // Return value is the HTTP return code. 200 means OK. < 0 means some local error. - int GET(const RequestParams &req, Buffer *output, RequestProgress *progress); - int GET(const RequestParams &req, Buffer *output, std::vector &responseHeaders, RequestProgress *progress); + int GET(const RequestParams &req, Buffer *output, net::RequestProgress *progress); + int GET(const RequestParams &req, Buffer *output, std::vector &responseHeaders, net::RequestProgress *progress); // Return value is the HTTP return code. - int POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, RequestProgress *progress); - int POST(const RequestParams &req, const std::string &data, Buffer *output, RequestProgress *progress); + int POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, net::RequestProgress *progress); + int POST(const RequestParams &req, const std::string &data, Buffer *output, net::RequestProgress *progress); // HEAD, PUT, DELETE aren't implemented yet, but can be done with SendRequest. - int SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, RequestProgress *progress); - int SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, RequestProgress *progress); - int ReadResponseHeaders(net::Buffer *readbuf, std::vector &responseHeaders, RequestProgress *progress); + int SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, net::RequestProgress *progress); + int SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, net::RequestProgress *progress); + int ReadResponseHeaders(net::Buffer *readbuf, std::vector &responseHeaders, net::RequestProgress *progress); // If your response contains a response, you must read it. - int ReadResponseEntity(net::Buffer *readbuf, const std::vector &responseHeaders, Buffer *output, RequestProgress *progress); + int ReadResponseEntity(net::Buffer *readbuf, const std::vector &responseHeaders, Buffer *output, net::RequestProgress *progress); void SetDataTimeout(double t) { dataTimeout_ = t; @@ -102,12 +94,26 @@ enum class RequestMethod { POST, }; +enum class ProgressBarMode { + NONE, + VISIBLE, + DELAYED, +}; + // Really an asynchronous request. class Download { public: - Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile); + Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode = ProgressBarMode::DELAYED); ~Download(); + void SetAccept(const char *mime) { + acceptMime_ = mime; + } + + void SetUserAgent(const std::string &userAgent) { + userAgent_ = userAgent; + } + void Start(); void Join(); @@ -125,10 +131,6 @@ public: std::string url() const { return url_; } const Path &outfile() const { return outfile_; } - void SetAccept(const char *mime) { - acceptMime_ = mime; - } - // If not downloading to a file, access this to get the result. Buffer &buffer() { return buffer_; } const Buffer &buffer() const { return buffer_; } @@ -141,7 +143,7 @@ public: return cancelled_; } - // NOTE: Callbacks are NOT executed until RunCallback is called. This is so that + // NOTE: Completion callbacks (which these are) are deferred until RunCallback is called. This is so that // the call will end up on the thread that calls g_DownloadManager.Update(). void SetCallback(std::function callback) { callback_ = callback; @@ -151,14 +153,8 @@ public: callback_(*this); } } - - // Just metadata. Convenient for download managers, for example, if set, - // Downloader::GetCurrentProgress won't return it in the results. - bool IsHidden() const { return hidden_; } - void SetHidden(bool hidden) { hidden_ = hidden; } - void SetUserAgent(const std::string &userAgent) { - userAgent_ = userAgent; - } + // Visual name for the download, to be displayed in progress bars. + void SetName(const std::string &&name) { name_ = std::move(name); } private: void Do(); // Actually does the download. Runs on thread. @@ -166,8 +162,8 @@ private: std::string RedirectLocation(const std::string &baseUrl); void SetFailed(int code); - RequestProgress progress_; RequestMethod method_; + net::RequestProgress progress_; std::string postData_; std::string userAgent_; Buffer buffer_; @@ -181,8 +177,9 @@ private: bool completed_ = false; bool failed_ = false; bool cancelled_ = false; - bool hidden_ = false; + ProgressBarMode progressBarMode_; bool joined_ = false; + std::string name_; std::function callback_; }; @@ -194,11 +191,12 @@ public: CancelAll(); } - std::shared_ptr StartDownload(const std::string &url, const Path &outfile, const char *acceptMime = nullptr); + std::shared_ptr StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime = nullptr); std::shared_ptr StartDownloadWithCallback( const std::string &url, const Path &outfile, + ProgressBarMode mode, std::function callback, const char *acceptMime = nullptr); @@ -206,6 +204,7 @@ public: const std::string &url, const std::string &postData, const std::string &postMime, // Use postMime = "application/x-www-form-urlencoded" for standard form-style posts, such as used by retroachievements. For encoding form data manually we have MultipartFormDataEncoder. + ProgressBarMode mode, std::function callback); // Drops finished downloads from the list. @@ -217,12 +216,6 @@ public: userAgent_ = userAgent; } - std::vector GetCurrentProgress(); - - size_t GetActiveCount() const { - return downloads_.size(); - } - private: std::vector> downloads_; // These get copied to downloads_ in Update(). It's so that callbacks can add new downloads diff --git a/Common/Net/NetBuffer.cpp b/Common/Net/NetBuffer.cpp index 17a5aae916..cf2f16706c 100644 --- a/Common/Net/NetBuffer.cpp +++ b/Common/Net/NetBuffer.cpp @@ -48,7 +48,7 @@ bool Buffer::FlushSocket(uintptr_t sock, double timeout, bool *cancelled) { return true; } -bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress, float *kBps, bool *cancelled) { +bool Buffer::ReadAllWithProgress(int fd, int knownSize, RequestProgress *progress) { static constexpr float CANCEL_INTERVAL = 0.25f; std::vector buf; // We're non-blocking and reading from an OS buffer, so try to read as much as we can at a time. @@ -64,8 +64,8 @@ bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress, float * int total = 0; while (true) { bool ready = false; - while (!ready && cancelled) { - if (*cancelled) + while (!ready && (!progress || !*progress->cancelled)) { + if (*progress->cancelled) return false; ready = fd_util::WaitUntilReady(fd, CANCEL_INTERVAL, false); } @@ -88,10 +88,10 @@ bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress, float * char *p = Append((size_t)retval); memcpy(p, &buf[0], retval); total += retval; - if (progress) - *progress = (float)total / (float)knownSize; - if (kBps) - *kBps = (float)(total / (time_now_d() - st)) / 1024.0f; + if (progress) { + progress->Update((float)total / (float)knownSize); + progress->kBps = (float)(total / (time_now_d() - st)) / 1024.0f; + } } return true; } @@ -114,4 +114,4 @@ int Buffer::Read(int fd, size_t sz) { return (int)received; } -} +} // namespace diff --git a/Common/Net/NetBuffer.h b/Common/Net/NetBuffer.h index 12a4ba37c8..bc49b97ab4 100644 --- a/Common/Net/NetBuffer.h +++ b/Common/Net/NetBuffer.h @@ -1,16 +1,35 @@ -#pragma once +#pragma once #include +#include #include "Common/Buffer.h" namespace net { +class RequestProgress { +public: + RequestProgress() {} + explicit RequestProgress(bool *c) : cancelled(c) {} + + void Update(float newProgress) { + progress = newProgress; + if (callback) { + callback(newProgress); + } + } + + float progress = 0.0f; + float kBps = 0.0f; + bool *cancelled = nullptr; + std::function callback; +}; + class Buffer : public ::Buffer { public: bool FlushSocket(uintptr_t sock, double timeout, bool *cancelled = nullptr); - bool ReadAllWithProgress(int fd, int knownSize, float *progress, float *kBps, bool *cancelled); + bool ReadAllWithProgress(int fd, int knownSize, RequestProgress *progress); // < 0: error // >= 0: number of bytes read diff --git a/Common/System/OSD.cpp b/Common/System/OSD.cpp index fd85dde9ca..f100737cc8 100644 --- a/Common/System/OSD.cpp +++ b/Common/System/OSD.cpp @@ -215,7 +215,7 @@ void OnScreenDisplay::ShowOnOff(const std::string &message, bool on, float durat Show(OSDType::MESSAGE_INFO, message + ": " + (on ? "on" : "off"), duration_s); } -void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress) { +void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, float minValue, float maxValue, float progress, float delay) { std::lock_guard guard(mutex_); double now = time_now_d(); bool found = false; @@ -236,16 +236,27 @@ void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, int bar.minValue = minValue; bar.maxValue = maxValue; bar.progress = progress; + bar.startTime = now + delay; bar.endTime = now + 60.0; // Show the progress bar for 60 seconds, then fade it out. bars_.push_back(bar); } -void OnScreenDisplay::RemoveProgressBar(std::string id) { +void OnScreenDisplay::RemoveProgressBar(std::string id, bool success, float delay_s) { std::lock_guard guard(mutex_); for (auto iter = bars_.begin(); iter != bars_.end(); iter++) { if (iter->id == id) { - iter->progress = iter->maxValue; - iter->endTime = time_now_d() + FadeoutTime(); + if (success) { + // Quickly shoot up to max, if we weren't there. + if (iter->maxValue != 0.0f) { + iter->progress = iter->maxValue; + } else { + // Fake a full progress + iter->minValue = 0; + iter->maxValue = 1; + iter->progress = 1; + } + } + iter->endTime = time_now_d() + delay_s + FadeoutTime(); break; } } diff --git a/Common/System/OSD.h b/Common/System/OSD.h index 928e1d44d6..6432cca01c 100644 --- a/Common/System/OSD.h +++ b/Common/System/OSD.h @@ -50,8 +50,8 @@ public: // Progress bar controls // Set is both create and update. If you set maxValue <= minValue, you'll create an "indeterminate" progress // bar that doesn't show a specific amount of progress. - void SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress); - void RemoveProgressBar(std::string id); + void SetProgressBar(std::string id, std::string &&message, float minValue, float maxValue, float progress, float delay_s); + void RemoveProgressBar(std::string id, bool success, float delay_s); // Call every frame to keep the sidebar visible. Otherwise it'll fade out. void NudgeSidebar(); @@ -74,9 +74,10 @@ public: struct ProgressBar { std::string id; std::string message; - int minValue; - int maxValue; - int progress; + float minValue; + float maxValue; + float progress; + double startTime; double endTime; }; diff --git a/Core/Config.cpp b/Core/Config.cpp index 030d828fda..316157d3e5 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1174,8 +1174,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { if (iRunCount % 10 == 0 && bCheckForNewVersion) { const char *versionUrl = "http://www.ppsspp.org/version.json"; const char *acceptMime = "application/json, text/*; q=0.9, */*; q=0.8"; - auto dl = g_DownloadManager.StartDownloadWithCallback(versionUrl, Path(), &DownloadCompletedCallback, acceptMime); - dl->SetHidden(true); + g_DownloadManager.StartDownloadWithCallback(versionUrl, Path(), http::ProgressBarMode::NONE, &DownloadCompletedCallback, acceptMime); } INFO_LOG(LOADER, "Loading controller config: %s", controllerIniFilename_.c_str()); diff --git a/Core/FileLoaders/HTTPFileLoader.h b/Core/FileLoaders/HTTPFileLoader.h index ec92b8b9d5..e4bf8ee9be 100644 --- a/Core/FileLoaders/HTTPFileLoader.h +++ b/Core/FileLoaders/HTTPFileLoader.h @@ -71,7 +71,7 @@ private: s64 filepos_ = 0; Url url_; http::Client client_; - http::RequestProgress progress_; + net::RequestProgress progress_; ::Path filename_; bool connected_ = false; bool cancel_ = false; diff --git a/Core/HLE/sceNp2.cpp b/Core/HLE/sceNp2.cpp index 59f7b0cfdf..89f047757f 100644 --- a/Core/HLE/sceNp2.cpp +++ b/Core/HLE/sceNp2.cpp @@ -131,7 +131,7 @@ static int sceNpMatching2ContextStart(int ctxId) //npMatching2Ctx.started = true; Url url("http://static-resource.np.community.playstation.net/np/resource/psp-title/" + std::string(npTitleId.data) + "_00/matching/" + std::string(npTitleId.data) + "_00-matching.xml"); http::Client client; - http::RequestProgress progress; + net::RequestProgress progress; if (!client.Resolve(url.Host().c_str(), url.Port())) { return hleLogError(SCENET, SCE_NP_COMMUNITY_SERVER_ERROR_NO_SUCH_TITLE, "HTTP failed to resolve %s", url.Resource().c_str()); } diff --git a/Core/Reporting.cpp b/Core/Reporting.cpp index bedcbf8d85..df6c7a84f5 100644 --- a/Core/Reporting.cpp +++ b/Core/Reporting.cpp @@ -272,7 +272,7 @@ namespace Reporting bool SendReportRequest(const char *uri, const std::string &data, const std::string &mimeType, Buffer *output = NULL) { http::Client http; - http::RequestProgress progress(&pendingMessagesDone); + net::RequestProgress progress(&pendingMessagesDone); Buffer theVoid = Buffer::Void(); http.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION)); diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index 97ec1efcd8..84710d136a 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -176,7 +176,7 @@ static void server_call_callback(const rc_api_request_t *request, { // If post data is provided, we need to make a POST request, otherwise, a GET request will suffice. if (request->post_data) { - g_DownloadManager.AsyncPostWithCallback(std::string(request->url), std::string(request->post_data), "application/x-www-form-urlencoded", [=](http::Download &download) { + g_DownloadManager.AsyncPostWithCallback(std::string(request->url), std::string(request->post_data), "application/x-www-form-urlencoded", http::ProgressBarMode::DELAYED, [=](http::Download &download) { std::string buffer; download.buffer().TakeAll(&buffer); rc_api_server_response_t response{}; @@ -186,7 +186,7 @@ static void server_call_callback(const rc_api_request_t *request, callback(&response, callback_data); }); } else { - g_DownloadManager.StartDownloadWithCallback(std::string(request->url), Path(), [=](http::Download &download) { + g_DownloadManager.StartDownloadWithCallback(std::string(request->url), Path(), http::ProgressBarMode::DELAYED, [=](http::Download &download) { std::string buffer; download.buffer().TakeAll(&buffer); rc_api_server_response_t response{}; @@ -394,7 +394,7 @@ static void login_password_callback(int result, const char *error_message, rc_cl } } - g_OSD.RemoveProgressBar("cheevos_async_login"); + g_OSD.RemoveProgressBar("cheevos_async_login", true, 0.1f); g_isLoggingIn = false; } @@ -403,7 +403,7 @@ bool LoginAsync(const char *username, const char *password) { if (IsLoggedIn() || std::strlen(username) == 0 || std::strlen(password) == 0 || IsUsingRAIntegration()) return false; - g_OSD.SetProgressBar("cheevos_async_login", di->T("Logging in..."), 0, 0, 0); + g_OSD.SetProgressBar("cheevos_async_login", di->T("Logging in..."), 0, 0, 0, 0.0f); g_isLoggingIn = true; rc_client_begin_login_with_password(g_rcClient, username, password, &login_password_callback, nullptr); @@ -540,7 +540,7 @@ bool HasAchievementsOrLeaderboards() { void DownloadImageIfMissing(const std::string &cache_key, std::string &&url) { if (g_iconCache.MarkPending(cache_key)) { INFO_LOG(ACHIEVEMENTS, "Downloading image: %s (%s)", url.c_str(), cache_key.c_str()); - g_DownloadManager.StartDownloadWithCallback(url, Path(), [cache_key](http::Download &download) { + g_DownloadManager.StartDownloadWithCallback(url, Path(), http::ProgressBarMode::NONE, [cache_key](http::Download &download) { if (download.ResultCode() != 200) return; std::string data; diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index 37b3b1dd6a..c8e09e4ab8 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -102,7 +102,7 @@ bool GameManager::DownloadAndInstall(std::string storeFileUrl) { Path filename = GetTempFilename(); const char *acceptMime = "application/zip, application/x-cso, application/x-iso9660-image, application/octet-stream; q=0.9, */*; q=0.8"; - curDownload_ = g_DownloadManager.StartDownload(storeFileUrl, filename, acceptMime); + curDownload_ = g_DownloadManager.StartDownload(storeFileUrl, filename, http::ProgressBarMode::VISIBLE, acceptMime); return true; } diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index 9db7b0f5fd..c530fdb83d 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -64,7 +64,7 @@ static ServerStatus RetrieveStatus() { static bool RegisterServer(int port) { bool success = false; http::Client http; - http::RequestProgress progress; + net::RequestProgress progress; Buffer theVoid = Buffer::Void(); http.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION)); diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 17169f6ed2..6c7e336ad3 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -819,19 +819,23 @@ void SystemInfoScreen::CreateTabs() { }); internals->Add(new ItemHeader(si->T("Progress tests"))); internals->Add(new Choice(si->T("30%")))->OnClick.Add([&](UI::EventParams &) { - g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 30); + g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 30, 0.0f); return UI::EVENT_DONE; }); internals->Add(new Choice(si->T("100%")))->OnClick.Add([&](UI::EventParams &) { - g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 100); + g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 100, 1.0f); return UI::EVENT_DONE; }); internals->Add(new Choice(si->T("N/A%")))->OnClick.Add([&](UI::EventParams &) { - g_OSD.SetProgressBar("testprogress", "Test Progress", 0, 0, 0); + g_OSD.SetProgressBar("testprogress", "Test Progress", 0, 0, 0, 0.0f); return UI::EVENT_DONE; }); - internals->Add(new Choice(si->T("Clear")))->OnClick.Add([&](UI::EventParams &) { - g_OSD.RemoveProgressBar("testprogress"); + internals->Add(new Choice(si->T("Success")))->OnClick.Add([&](UI::EventParams &) { + g_OSD.RemoveProgressBar("testprogress", true, 0.5f); + return UI::EVENT_DONE; + }); + internals->Add(new Choice(si->T("Failure")))->OnClick.Add([&](UI::EventParams &) { + g_OSD.RemoveProgressBar("testprogress", false, 0.5f); return UI::EVENT_DONE; }); internals->Add(new ItemHeader(si->T("Achievement tests"))); @@ -1336,7 +1340,7 @@ void FrameDumpTestScreen::update() { if (!listing_) { const char *acceptMime = "text/html, */*; q=0.8"; - listing_ = g_DownloadManager.StartDownload(framedumpsBaseUrl, Path(), acceptMime); + listing_ = g_DownloadManager.StartDownload(framedumpsBaseUrl, Path(), http::ProgressBarMode::DELAYED, acceptMime); } if (listing_ && listing_->Done() && files_.empty()) { diff --git a/UI/OnScreenDisplay.cpp b/UI/OnScreenDisplay.cpp index fec95d5365..7d5b2a0ee0 100644 --- a/UI/OnScreenDisplay.cpp +++ b/UI/OnScreenDisplay.cpp @@ -292,13 +292,9 @@ void OnScreenMessagesView::Draw(UIContext &dc) { } } - // Get height - float w, h; - dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, "Wg", &w, &h); - y = 10.0f; - // Then draw them all. + // Draw the progress bars. const std::vector bars = g_OSD.ProgressBars(); for (auto &bar : bars) { float tw, th; @@ -306,11 +302,14 @@ void OnScreenMessagesView::Draw(UIContext &dc) { Bounds b(0.0f, y, tw, th); b.x = (bounds_.w - b.w) * 0.5f; - float alpha = Clamp((float)(bar.endTime - now) * 4.0f, 0.0f, 1.0f); + float enterAlpha = saturatef((float)(now - bar.startTime) * 4.0f); + float leaveAlpha = saturatef((float)(bar.endTime - now) * 4.0f); + float alpha = std::min(enterAlpha, leaveAlpha); RenderOSDProgressBar(dc, bar, b, 0, alpha); y += (b.h + 4.0f) * alpha; // including alpha here gets us smooth animations. } + // Draw the rest of the top-center messages. const std::vector entries = g_OSD.Entries(); for (const auto &entry : entries) { dc.SetFontScale(1.0f, 1.0f); @@ -365,28 +364,6 @@ void OnScreenMessagesView::Draw(UIContext &dc) { RenderOSDEntry(dc, entry, b, h1, align, alpha); y += (b.h * scale + 4.0f) * alpha; // including alpha here gets us smooth animations. } - - // Thin bar at the top of the screen. - // TODO: Remove and replace with "proper" progress bars. - std::vector progress = g_DownloadManager.GetCurrentProgress(); - if (!progress.empty()) { - static const uint32_t colors[4] = { - 0xFFFFFFFF, - 0xFFCCCCCC, - 0xFFAAAAAA, - 0xFF777777, - }; - - dc.Begin(); - int h = 5; - for (size_t i = 0; i < progress.size(); i++) { - float barWidth = 10 + (dc.GetBounds().w - 10) * progress[i]; - Bounds bounds(0, h * i, barWidth, h); - UI::Drawable solid(colors[i & 3]); - dc.FillRect(solid, bounds); - } - dc.Flush(); - } } std::string OnScreenMessagesView::DescribeText() const { diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 5436750cc5..b80df30021 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -139,7 +139,7 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort } SetStatus("Loading game list from [URL]...", host, port); - http::RequestProgress progress(&scanCancelled); + net::RequestProgress progress(&scanCancelled); code = http.GET(http::RequestParams(subdir.c_str()), &result, &progress); http.Disconnect(); @@ -193,7 +193,7 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort SetStatus("Looking for peers...", "", 0); if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) { if (http.Connect(2, 20.0, &scanCancelled)) { - http::RequestProgress progress(&scanCancelled); + net::RequestProgress progress(&scanCancelled); code = http.GET(http::RequestParams("/match/list"), &result, &progress); http.Disconnect(); } diff --git a/UI/Store.cpp b/UI/Store.cpp index 2682b171cb..0ad19143f6 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -58,7 +58,7 @@ public: if (useIconCache && g_iconCache.MarkPending(path_)) { const char *acceptMime = "image/png, image/jpeg, image/*; q=0.9, */*; q=0.8"; - downloader_->StartDownloadWithCallback(path_, Path(), [&](http::Download &download) { + downloader_->StartDownloadWithCallback(path_, Path(), http::ProgressBarMode::DELAYED, [&](http::Download &download) { if (download.ResultCode() == 200) { std::string data; download.buffer().TakeAll(&data); @@ -165,8 +165,7 @@ void HttpImageFileView::Draw(UIContext &dc) { if (!texture_ && !textureFailed_ && !path_.empty() && !download_) { auto cb = std::bind(&HttpImageFileView::DownloadCompletedCallback, this, std::placeholders::_1); const char *acceptMime = "image/png, image/jpeg, image/*; q=0.9, */*; q=0.8"; - download_ = downloader_->StartDownloadWithCallback(path_, Path(), cb, acceptMime); - download_->SetHidden(true); + downloader_->StartDownloadWithCallback(path_, Path(), http::ProgressBarMode::NONE, cb, acceptMime); } if (!textureData_.empty()) { @@ -404,7 +403,7 @@ StoreScreen::StoreScreen() { std::string indexPath = storeBaseUrl + "index.json"; const char *acceptMime = "application/json, */*; q=0.8"; - listing_ = g_DownloadManager.StartDownload(indexPath, Path(), acceptMime); + listing_ = g_DownloadManager.StartDownload(indexPath, Path(), http::ProgressBarMode::DELAYED, acceptMime); } StoreScreen::~StoreScreen() { From 370bb4c3154813bf975a45599e59872089e1e405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 18 Jul 2023 15:52:14 +0200 Subject: [PATCH 009/167] More progressbar improvements --- Common/Net/HTTPClient.cpp | 50 ++++++++++++++++++-------------------- Common/Net/HTTPClient.h | 8 +++--- Common/Net/NetBuffer.cpp | 2 +- Common/Net/NetBuffer.h | 12 ++++++--- Common/System/OSD.cpp | 11 ++++++++- Core/RetroAchievements.cpp | 9 ++++--- assets/lang/en_US.ini | 1 + 7 files changed, 52 insertions(+), 41 deletions(-) diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index 98c70db433..83f4bf2083 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -303,7 +303,7 @@ int Client::SendRequest(const char *method, const RequestParams &req, const char } int Client::SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, net::RequestProgress *progress) { - progress->Update(0.01f); + progress->Update(0, 0, false); net::Buffer buffer; const char *tpl = @@ -410,17 +410,11 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vectorUpdate(0.02f); - } - if (!readbuf->ReadAllWithProgress(sock(), contentLength, progress)) return -1; @@ -428,7 +422,6 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vectorIsVoid()) { if (chunked) { DeChunk(readbuf, output, contentLength); - progress->Update(1.0f); } else { output->Append(*readbuf); } @@ -440,42 +433,45 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vectorUpdate(0.0f); // TJDO: is this right? + progress->Update(0, 0, true); return -1; } output->Append(decompressed); } } - progress->Update(1.0f); + progress->Update(contentLength, contentLength, true); return 0; } -Download::Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode) - : method_(method), progress_(&cancelled_), url_(url), postData_(postData), postMime_(postMime), outfile_(outfile), progressBarMode_(progressBarMode) { - - progress_.callback = [=](float progress) { +Download::Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode, const std::string &name) + : method_(method), progress_(&cancelled_), url_(url), postData_(postData), postMime_(postMime), outfile_(outfile), progressBarMode_(progressBarMode), name_(name) { + progress_.callback = [=](int64_t bytes, int64_t contentLength, bool done) { std::string message; if (!name_.empty()) { message = name_; } else { - std::size_t pos = url.rfind('/'); + std::size_t pos = url_.rfind('/'); if (pos != std::string::npos) { - message = url.substr(pos + 1); + message = url_.substr(pos + 1); } else { - message = url; + message = url_; } } if (progressBarMode_ != ProgressBarMode::NONE) { - g_OSD.SetProgressBar(url, std::move(message), 0.0f, 1.0f, progress, progressBarMode_ == ProgressBarMode::DELAYED ? 1.0f : 0.0f); // delay 0.5 seconds before showing. - if (progress == 1.0f) { - g_OSD.RemoveProgressBar(url, Failed() ? false : true, 0.5f); + INFO_LOG(IO, "Showing progress bar: %s", message.c_str()); + if (!done) { + g_OSD.SetProgressBar(url_, std::move(message), 0.0f, (float)contentLength, (float)bytes, progressBarMode_ == ProgressBarMode::DELAYED ? 3.0f : 0.0f); // delay 3 seconds before showing. + } else { + g_OSD.RemoveProgressBar(url_, Failed() ? false : true, 0.5f); } } }; } Download::~Download() { + g_OSD.RemoveProgressBar(url_, Failed() ? false : true, 0.5f); + _assert_msg_(joined_, "Download destructed without join"); } @@ -493,7 +489,7 @@ void Download::Join() { void Download::SetFailed(int code) { failed_ = true; - progress_.Update(1.0f); + progress_.Update(0, 0, true); completed_ = true; } @@ -588,8 +584,6 @@ void Download::Do() { resultCode_ = resultCode; } - progress_.Update(1.0f); - // Set this last to ensure no race conditions when checking Done. Users must always check // Done before looking at the result code. completed_ = true; @@ -612,8 +606,9 @@ std::shared_ptr Downloader::StartDownloadWithCallback( const Path &outfile, ProgressBarMode mode, std::function callback, + const std::string &name, const char *acceptMime) { - std::shared_ptr dl(new Download(RequestMethod::GET, url, "", "", outfile, mode)); + std::shared_ptr dl(new Download(RequestMethod::GET, url, "", "", outfile, mode, name)); if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); if (acceptMime) @@ -629,8 +624,9 @@ std::shared_ptr Downloader::AsyncPostWithCallback( const std::string &postData, const std::string &postMime, ProgressBarMode mode, - std::function callback) { - std::shared_ptr dl(new Download(RequestMethod::POST, url, postData, postMime, Path(), mode)); + std::function callback, + const std::string &name) { + std::shared_ptr dl(new Download(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); dl->SetCallback(callback); diff --git a/Common/Net/HTTPClient.h b/Common/Net/HTTPClient.h index 8987e3331e..88b76f116e 100644 --- a/Common/Net/HTTPClient.h +++ b/Common/Net/HTTPClient.h @@ -103,7 +103,7 @@ enum class ProgressBarMode { // Really an asynchronous request. class Download { public: - Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode = ProgressBarMode::DELAYED); + Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode = ProgressBarMode::DELAYED, const std::string &name = ""); ~Download(); void SetAccept(const char *mime) { @@ -153,8 +153,6 @@ public: callback_(*this); } } - // Visual name for the download, to be displayed in progress bars. - void SetName(const std::string &&name) { name_ = std::move(name); } private: void Do(); // Actually does the download. Runs on thread. @@ -198,6 +196,7 @@ public: const Path &outfile, ProgressBarMode mode, std::function callback, + const std::string &name = "", const char *acceptMime = nullptr); std::shared_ptr AsyncPostWithCallback( @@ -205,7 +204,8 @@ public: const std::string &postData, const std::string &postMime, // Use postMime = "application/x-www-form-urlencoded" for standard form-style posts, such as used by retroachievements. For encoding form data manually we have MultipartFormDataEncoder. ProgressBarMode mode, - std::function callback); + std::function callback, + const std::string &name = ""); // Drops finished downloads from the list. void Update(); diff --git a/Common/Net/NetBuffer.cpp b/Common/Net/NetBuffer.cpp index cf2f16706c..3cdbbcd189 100644 --- a/Common/Net/NetBuffer.cpp +++ b/Common/Net/NetBuffer.cpp @@ -89,7 +89,7 @@ bool Buffer::ReadAllWithProgress(int fd, int knownSize, RequestProgress *progres memcpy(p, &buf[0], retval); total += retval; if (progress) { - progress->Update((float)total / (float)knownSize); + progress->Update(total, knownSize, false); progress->kBps = (float)(total / (time_now_d() - st)) / 1024.0f; } } diff --git a/Common/Net/NetBuffer.h b/Common/Net/NetBuffer.h index bc49b97ab4..85b1421d28 100644 --- a/Common/Net/NetBuffer.h +++ b/Common/Net/NetBuffer.h @@ -12,17 +12,21 @@ public: RequestProgress() {} explicit RequestProgress(bool *c) : cancelled(c) {} - void Update(float newProgress) { - progress = newProgress; + void Update(int64_t downloaded, int64_t totalBytes, bool done) { + if (totalBytes) { + progress = (double)downloaded / (double)totalBytes; + } else { + progress = 0.01f; + } if (callback) { - callback(newProgress); + callback(downloaded, totalBytes, done); } } float progress = 0.0f; float kBps = 0.0f; bool *cancelled = nullptr; - std::function callback; + std::function callback; }; class Buffer : public ::Buffer { diff --git a/Common/System/OSD.cpp b/Common/System/OSD.cpp index f100737cc8..ab1b900e69 100644 --- a/Common/System/OSD.cpp +++ b/Common/System/OSD.cpp @@ -216,9 +216,14 @@ void OnScreenDisplay::ShowOnOff(const std::string &message, bool on, float durat } void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, float minValue, float maxValue, float progress, float delay) { - std::lock_guard guard(mutex_); + _dbg_assert_(!my_isnanorinf(progress)); + _dbg_assert_(!my_isnanorinf(minValue)); + _dbg_assert_(!my_isnanorinf(maxValue)); + double now = time_now_d(); bool found = false; + + std::lock_guard guard(mutex_); for (auto &bar : bars_) { if (bar.id == id) { bar.minValue = minValue; @@ -230,6 +235,10 @@ void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, floa } } + if (message == "dorequest.php") { + found = found; + } + ProgressBar bar; bar.id = id; bar.message = std::move(message); diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index 84710d136a..84302c8123 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -175,8 +175,9 @@ static void server_call_callback(const rc_api_request_t *request, rc_client_server_callback_t callback, void *callback_data, rc_client_t *client) { // If post data is provided, we need to make a POST request, otherwise, a GET request will suffice. + auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS); if (request->post_data) { - g_DownloadManager.AsyncPostWithCallback(std::string(request->url), std::string(request->post_data), "application/x-www-form-urlencoded", http::ProgressBarMode::DELAYED, [=](http::Download &download) { + std::shared_ptr download = g_DownloadManager.AsyncPostWithCallback(std::string(request->url), std::string(request->post_data), "application/x-www-form-urlencoded", http::ProgressBarMode::DELAYED, [=](http::Download &download) { std::string buffer; download.buffer().TakeAll(&buffer); rc_api_server_response_t response{}; @@ -184,9 +185,9 @@ static void server_call_callback(const rc_api_request_t *request, response.body_length = buffer.size(); response.http_status_code = download.ResultCode(); callback(&response, callback_data); - }); + }, ac->T("Contacting RetroAchievements server...")); } else { - g_DownloadManager.StartDownloadWithCallback(std::string(request->url), Path(), http::ProgressBarMode::DELAYED, [=](http::Download &download) { + std::shared_ptr download = g_DownloadManager.StartDownloadWithCallback(std::string(request->url), Path(), http::ProgressBarMode::DELAYED, [=](http::Download &download) { std::string buffer; download.buffer().TakeAll(&buffer); rc_api_server_response_t response{}; @@ -194,7 +195,7 @@ static void server_call_callback(const rc_api_request_t *request, response.body_length = buffer.size(); response.http_status_code = download.ResultCode(); callback(&response, callback_data); - }); + }, ac->T("Contacting RetroAchievements server...")); } } diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index ec4b256d5f..3462f24c2f 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -32,6 +32,7 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode From 3770b3af04eef11aa0a1b22a1be49037e97bce7a Mon Sep 17 00:00:00 2001 From: DDinghoya Date: Wed, 19 Jul 2023 23:07:47 +0900 Subject: [PATCH 010/167] Update ko_KR.ini --- assets/lang/ko_KR.ini | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/assets/lang/ko_KR.ini b/assets/lang/ko_KR.ini index 073b3d318c..f6885ee161 100644 --- a/assets/lang/ko_KR.ini +++ b/assets/lang/ko_KR.ini @@ -4,7 +4,7 @@ %1: Leaderboard attempt failed = %1: 순위표 시도 실패 #%1: Submitting leaderboard score: %2! = %1: 리더보드 점수 제출: %2! Account = 계정 -Achievement Unlocked = Achievement Unlocked +Achievement Unlocked = 성과 잠금 해제 Achievements = 성과 Achievements are disabled = 성과 비활성화 Challenge Mode = 도전 모드 @@ -13,14 +13,14 @@ Customize = Customize Earned = %d 의 %d 성과 및 %d 의 %d 포인트 획득 Encore Mode = 앙코르 모드 How to use RetroAchievements = RetroAchievements 사용 방법 -Leaderboard score submission = Leaderboard score submission +Leaderboard score submission = 순위표 점수 제출 Leaderboard submission is enabled = 순위표 제출 활성화 Leaderboards = 순위표 Links = 링크 Locked achievements = 잠겨진 성과 Log bad memory accesses = 불량 메모리 접속 로그 Mastered %1 = 마스터한 %1 -Submitted %1 for %2 = Submitted %1 for %2 +Submitted %1 for %2 = 2에 %1을(를) 제출 This feature is not available in Challenge Mode = 도전 모드에서는 이 기능을 사용할 수 없음 Save states not available in Challenge Mode = 도전 모드에서 사용할 수 없는 저장 상태 Register on www.retroachievements.org = www.retroachievements.org에 등록 @@ -340,8 +340,8 @@ Edit = 편집 Enable All = 모두 활성화 Enabled = 활성화 Enter = 입력 -Failed to connect to server, check your internet connection. = Failed to connect to server, check your internet connection. -Failed to log in, check your username and password. = Failed to log in, check your username and password. +Failed to connect to server, check your internet connection. = 서버에 연결하지 못했습니다. 인터넷 연결을 확인하세요. +Failed to log in, check your username and password. = 로그인에 실패했습니다. 사용자 이름과 비밀번호를 확인하세요. Filter = 필터 Finish = 마침 GE Frame Dumps = GE 프레임 덤프 @@ -354,8 +354,8 @@ Loading = 불러오기 중\n잠시만 기다려 주세요... LoadingFailed = 데이터를 불러올 수 없습니다. Log in = 로그인 Log out = 로그아웃 -Logged in! = Logged in! -Logging in... = Logging in... +Logged in! = 로그인했습니다! +Logging in... = 로그인 중... Move = 이동 Move Up = 위로 이동 Move Down = 아래로 이동 @@ -767,7 +767,7 @@ Bottom Right = 하단 오른쪽 Center Left = 중앙 왼쪽 Center Right = 중앙 오른쪽 Change Mac Address = MAC 주소 변경 -ChangeMacSaveConfirm = 새로운 MAC 주소를 생성하시겠습니까? +ChangeMacSaveConfirm = 새로운 MAC 주소를 생성하겠습니까? ChangeMacSaveWarning = 일부 게임은 저장 데이터를 불러올 때 MAC 주소를 확인하므로 이전 저장이 손상될 수 있습니다. Change proAdhocServer Address = PRO ad hoc 서버 IP 주소 변경 (로컬호스트 = 다중 인스턴스) Chat = 채팅 @@ -1049,7 +1049,7 @@ Save State Failed = 상태 저장 실패! Saved State = 저장된 상태 saveNewTextures_false = 텍스처 저장이 비활성화되었습니다. saveNewTextures_true = 이제 텍스처가 저장 공간에 저장됩니다. -SpeedCustom2 = Speed: 대체 2 +SpeedCustom2 = 속도: 대체 2 standard = 속도: 표준 State load undone = 상태 불러오기 취소 Untitled PSP game = 제목 없는 PSP 게임 From 85f5136a81b0511efb7707447f5cc9a3ea7e4e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 20 Jul 2023 09:56:51 +0200 Subject: [PATCH 011/167] Add a GetProperty query for the git version --- Common/System/System.h | 1 + Common/UI/PopupScreens.h | 3 +++ Common/UI/UIScreen.cpp | 3 +-- Qt/QtMain.cpp | 2 ++ SDL/SDLMain.cpp | 2 ++ UWP/PPSSPP_UWPMain.cpp | 2 ++ Windows/main.cpp | 2 ++ android/jni/app-android.cpp | 2 ++ ios/main.mm | 2 ++ 9 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Common/System/System.h b/Common/System/System.h index 14001fefa7..fd1ca445a5 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -117,6 +117,7 @@ enum SystemProperty { SYSPROP_BOARDNAME, SYSPROP_CLIPBOARD_TEXT, SYSPROP_GPUDRIVER_VERSION, + SYSPROP_BUILD_VERSION, // Separate SD cards or similar. // Need hacky solutions to get at this. diff --git a/Common/UI/PopupScreens.h b/Common/UI/PopupScreens.h index fa2f2e00d9..8fdfb68e1f 100644 --- a/Common/UI/PopupScreens.h +++ b/Common/UI/PopupScreens.h @@ -55,6 +55,9 @@ class MessagePopupScreen : public PopupScreen { public: MessagePopupScreen(std::string title, std::string message, std::string button1, std::string button2, std::function callback) : PopupScreen(title, button1, button2), message_(message), callback_(callback) {} + + const char *tag() const override { return "MessagePopupScreen"; } + UI::Event OnChoice; protected: diff --git a/Common/UI/UIScreen.cpp b/Common/UI/UIScreen.cpp index 9fe4e44e1d..62ad927e55 100644 --- a/Common/UI/UIScreen.cpp +++ b/Common/UI/UIScreen.cpp @@ -1,5 +1,6 @@ #include #include "Common/System/Display.h" +#include "Common/System/System.h" #include "Common/Input/InputState.h" #include "Common/Input/KeyCodes.h" #include "Common/Math/curves.h" @@ -9,7 +10,6 @@ #include "Common/UI/Root.h" #include "Common/Data/Text/I18n.h" #include "Common/Render/DrawBuffer.h" - #include "Common/Log.h" static const bool ClickDebug = false; @@ -426,7 +426,6 @@ void PopupScreen::CreateViews() { CreatePopupContents(box_); root_->SetDefaultFocusView(box_); - if (ShowButtons() && !button1_.empty()) { // And the two buttons at the bottom. LinearLayout *buttonRow = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(200, WRAP_CONTENT)); diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index 098ed7e15b..a787d95d6d 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -161,6 +161,8 @@ std::string System_GetProperty(SystemProperty prop) { return result; } #endif + case SYSPROP_BUILD_VERSION: + return PPSSPP_GIT_VERSION; default: return ""; } diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index e3178aa102..f46af287ad 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -431,6 +431,8 @@ std::string System_GetProperty(SystemProperty prop) { } return result; } + case SYSPROP_BUILD_VERSION: + return PPSSPP_GIT_VERSION; default: return ""; } diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 7559148a49..ac5d8d4819 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -347,6 +347,8 @@ std::string System_GetProperty(SystemProperty prop) { return ""; case SYSPROP_GPUDRIVER_VERSION: return ""; + case SYSPROP_BUILD_VERSION: + return PPSSPP_GIT_VERSION; default: return ""; } diff --git a/Windows/main.cpp b/Windows/main.cpp index e46931cff7..7fe8de2ddc 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -234,6 +234,8 @@ std::string System_GetProperty(SystemProperty prop) { gpuDriverVersion = GetVideoCardDriverVersion(); } return gpuDriverVersion; + case SYSPROP_BUILD_VERSION: + return PPSSPP_GIT_VERSION; default: return ""; } diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 6574ef63c1..ca80a429ba 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -412,6 +412,8 @@ std::string System_GetProperty(SystemProperty prop) { return mogaVersion; case SYSPROP_BOARDNAME: return boardName; + case SYSPROP_BUILD_VERSION: + return PPSSPP_GIT_VERSION; default: return ""; } diff --git a/ios/main.mm b/ios/main.mm index e90b60e8a1..5d18ade897 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -108,6 +108,8 @@ std::string System_GetProperty(SystemProperty prop) { return StringFromFormat("iOS %s", version.c_str()); case SYSPROP_LANGREGION: return [[[NSLocale currentLocale] objectForKey:NSLocaleIdentifier] UTF8String]; + case SYSPROP_BUILD_VERSION: + return PPSSPP_GIT_VERSION; default: return ""; } From c5e3d3117f61fa9d6e7aa67e2ae933371d499851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 20 Jul 2023 10:57:10 +0200 Subject: [PATCH 012/167] Put the PPSSPP version on PromptScreen in the bottom left corner --- UI/MiscScreens.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 5046c09ce1..9b30a8bdc3 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -542,8 +542,14 @@ void PromptScreen::CreateViews() { Choice *yesButton = rightColumnItems->Add(new Choice(yesButtonText_)); yesButton->OnClick.Handle(this, &PromptScreen::OnYes); root_->SetDefaultFocusView(yesButton); - if (!noButtonText_.empty()) + if (!noButtonText_.empty()) { rightColumnItems->Add(new Choice(noButtonText_))->OnClick.Handle(this, &PromptScreen::OnNo); + } else { + // This is an information screen, not a question. + // Sneak in the version of PPSSPP in the corner, for debug-reporting user screenshots. + std::string version = System_GetProperty(SYSPROP_BUILD_VERSION); + root_->Add(new TextView(version, 0, true, new AnchorLayoutParams(10.0f, NONE, NONE, 10.0f))); + } } UI::EventReturn PromptScreen::OnYes(UI::EventParams &e) { From caebef63a0ec46c979634e5ff0f3779a95597e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 20 Jul 2023 11:26:20 +0200 Subject: [PATCH 013/167] iOS buildfix --- ios/main.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/ios/main.mm b/ios/main.mm index 5d18ade897..18d23f8517 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -21,6 +21,7 @@ #include "Common/System/Request.h" #include "Common/StringUtils.h" #include "Common/Profiler/Profiler.h" +#include "Core/Config.h" #include "UI/DarwinFileSystemServices.h" static int (*csops)(pid_t pid, unsigned int ops, void * useraddr, size_t usersize); From ace217008a332f1fe65db047e07507301ce22429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 20 Jul 2023 19:44:00 +0200 Subject: [PATCH 014/167] In D3D11, force block compressed textures to have dimensions divisible by 4 Fixes #17745 (crash when loading certain texture packs in D3D11) This is an old unfortunate limitation. Only applies to the top mip level, which makes it obvious that it's kinda unnecessary for the hardware and indeed, Vulkan and OpenGL don't have this limitation. --- GPU/Common/ReplacedTexture.cpp | 10 ++++++++++ GPU/Common/TextureReplacer.cpp | 3 +++ GPU/D3D11/TextureCacheD3D11.cpp | 8 ++++++++ assets/lang/en_US.ini | 1 + 4 files changed, 22 insertions(+) diff --git a/GPU/Common/ReplacedTexture.cpp b/GPU/Common/ReplacedTexture.cpp index 53668c89c1..d95ee297fb 100644 --- a/GPU/Common/ReplacedTexture.cpp +++ b/GPU/Common/ReplacedTexture.cpp @@ -513,6 +513,10 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference bool bc = Draw::DataFormatIsBlockCompressed(*pixelFormat, &blockSize); _dbg_assert_(bc || *pixelFormat == Draw::DataFormat::R8G8B8A8_UNORM); + if (bc && (level.w & 3) != 0 || (level.h & 3) != 0) { + WARN_LOG(G3D, "Block compressed replacement texture '%s' not divisible by 4x4 (%dx%d). In D3D11 (only!) we will have to expand (potentially causing glitches).", filename.c_str(), level.w, level.h); + } + data_.resize(numMips); basist::ktx2_transcoder_state transcodeState; // Each thread needs one of these. @@ -546,6 +550,7 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference } transcoder.clear(); vfs_->CloseFile(openFile); + return LoadLevelResult::DONE; // don't read more levels } else if (imageType == ReplacedImageType::DDS) { // TODO: Do better with alphaStatus, it's possible. @@ -562,6 +567,10 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference bool bc = Draw::DataFormatIsBlockCompressed(*pixelFormat, &blockSize); _dbg_assert_(bc); + if (bc && (level.w & 3) != 0 || (level.h & 3) != 0) { + WARN_LOG(G3D, "Block compressed replacement texture '%s' not divisible by 4x4 (%dx%d). In D3D11 (only!) we will have to expand (potentially causing glitches).", filename.c_str(), level.w, level.h); + } + data_.resize(numMips); // A DDS File can contain multiple mipmaps. @@ -583,6 +592,7 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference level.fileRef = nullptr; // We only provide a fileref on level 0 if we have mipmaps. } vfs_->CloseFile(openFile); + return LoadLevelResult::DONE; // don't read more levels } else if (imageType == ReplacedImageType::ZIM) { diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index da0f94401c..5e7b471c62 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -337,6 +337,9 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverri } } + auto gr = GetI18NCategory(I18NCat::GRAPHICS); + + g_OSD.Show(OSDType::MESSAGE_SUCCESS, gr->T("Texture replacement pack activated")); return true; } diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index d658d19a01..465981e673 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -348,6 +348,14 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) { if (th > 16384) th = 16384; + // NOTE: For block-compressed textures, we'll force the size up to the closest 4x4. This is due to an + // unfortunate restriction in D3D11 (and early D3D12). We'll warn about it in the log to give texture pack + // authors notice to fix it. + if (plan.doReplace && Draw::DataFormatIsBlockCompressed(plan.replaced->Format(), nullptr)) { + tw = (tw + 3) & ~3; + th = (th + 3) & ~3; + } + if (plan.depth == 1) { // We don't yet have mip generation, so clamp the number of levels to the ones we can load directly. levels = std::min(plan.levelsToCreate, plan.levelsToLoad); diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 3462f24c2f..7c0999fe1d 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -630,6 +630,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = Texture filtering Texture Filtering = Texture filtering +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Texture scaling Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported From b54d5ab8e628118d83d0533d6c5ccdbb7e5c6f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 20 Jul 2023 23:55:35 +0200 Subject: [PATCH 015/167] Cleanup the g_PortManager shutdown process Following up comment by @ANR2ME in #17744, to be merged before that one. --- Core/Util/PortManager.cpp | 10 ++++------ Core/Util/PortManager.h | 15 +++++++++------ UI/NativeApp.cpp | 3 +++ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Core/Util/PortManager.cpp b/Core/Util/PortManager.cpp index e8af736825..2e70d89233 100644 --- a/Core/Util/PortManager.cpp +++ b/Core/Util/PortManager.cpp @@ -54,16 +54,16 @@ PortManager::PortManager(): m_InitState(UPNP_INITSTATE_NONE), m_LocalPort(UPNP_LOCAL_PORT_ANY), m_leaseDuration("43200") { - // Since WSAStartup can be used multiple times it should be safe to do this right? - net::Init(); } PortManager::~PortManager() { // FIXME: On Windows it seems using any UPnP functions in this destructor that gets triggered when exiting PPSSPP will resulting to UPNPCOMMAND_HTTP_ERROR due to early WSACleanup (miniupnpc was getting WSANOTINITIALISED internally) +} + +void PortManager::Shutdown() { Clear(); Restore(); Terminate(); - net::Shutdown(); } void PortManager::Terminate() { @@ -502,9 +502,7 @@ int upnpService(const unsigned int timeout) // Cleaning up regardless of g_Config.bEnableUPnP to prevent lingering open ports on the router if (g_PortManager.GetInitState() == UPNP_INITSTATE_DONE) { - g_PortManager.Clear(); - g_PortManager.Restore(); - g_PortManager.Terminate(); + g_PortManager.Shutdown(); } // Should we ingore any leftover UPnP requests? instead of processing it on the next game start diff --git a/Core/Util/PortManager.h b/Core/Util/PortManager.h index 67f99a8fc5..f284788a38 100644 --- a/Core/Util/PortManager.h +++ b/Core/Util/PortManager.h @@ -83,9 +83,6 @@ public: // timeout: milliseconds to wait for a router to respond (default = 2000 ms) bool Initialize(const unsigned int timeout = 2000); - // Uninitialize/Reset the state - void Terminate(); - // Get UPnP Initialization status int GetInitState(); @@ -95,16 +92,22 @@ public: // Remove a port mapping (external port) bool Remove(const char* protocol, unsigned short port); + // Call on exit. Does a full shutdown. + void Shutdown(); + +private: + // Retrieves port lists mapped by PPSSPP for current LAN IP & other's applications + bool RefreshPortList(); + // Removes any lingering mapped ports created by PPSSPP (including from previous crashes) bool Clear(); // Restore ports mapped by others that were taken by PPSSPP, better used after Clear() bool Restore(); - // Retrieves port lists mapped by PPSSPP for current LAN IP & other's applications - bool RefreshPortList(); + // Uninitialize/Reset the state + void Terminate(); -protected: struct UPNPUrls* urls = NULL; struct IGDdatas* datas = NULL; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 9b4c6ba3e4..c06aa59fa9 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -103,6 +103,7 @@ #include "Core/HLE/proAdhoc.h" #include "Core/HW/MemoryStick.h" #include "Core/Util/GameManager.h" +#include "Core/Util/PortManager.h" #include "Core/Util/AudioFormat.h" #include "Core/WebServer.h" #include "Core/TiltEventProcessor.h" @@ -1409,6 +1410,8 @@ void NativeShutdown() { System_ExitApp(); #endif + g_PortManager.Shutdown(); + net::Shutdown(); g_Discord.Shutdown(); From 64eff0dc0ba0f2580b6d5c22ba24861082b7c26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 19 Jul 2023 00:46:32 +0200 Subject: [PATCH 016/167] Insert a virtual base class for Download --- Common/Net/HTTPClient.cpp | 26 ++++----- Common/Net/HTTPClient.h | 113 +++++++++++++++++++++++--------------- 2 files changed, 83 insertions(+), 56 deletions(-) diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index 83f4bf2083..9974fd6888 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -444,8 +444,8 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector Downloader::StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime) { - std::shared_ptr dl(new Download(RequestMethod::GET, url, "", "", outfile, mode)); + std::shared_ptr dl(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode)); if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); @@ -608,7 +608,7 @@ std::shared_ptr Downloader::StartDownloadWithCallback( std::function callback, const std::string &name, const char *acceptMime) { - std::shared_ptr dl(new Download(RequestMethod::GET, url, "", "", outfile, mode, name)); + std::shared_ptr dl(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode, name)); if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); if (acceptMime) @@ -626,7 +626,7 @@ std::shared_ptr Downloader::AsyncPostWithCallback( ProgressBarMode mode, std::function callback, const std::string &name) { - std::shared_ptr dl(new Download(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); + std::shared_ptr dl(new HTTPDownload(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); dl->SetCallback(callback); diff --git a/Common/Net/HTTPClient.h b/Common/Net/HTTPClient.h index 88b76f116e..4235d0e13c 100644 --- a/Common/Net/HTTPClient.h +++ b/Common/Net/HTTPClient.h @@ -100,48 +100,13 @@ enum class ProgressBarMode { DELAYED, }; -// Really an asynchronous request. class Download { public: - Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode = ProgressBarMode::DELAYED, const std::string &name = ""); - ~Download(); + Download(const std::string &url, const std::string &name, bool *cancelled) : url_(url), name_(name), progress_(cancelled) {} + virtual ~Download() {} - void SetAccept(const char *mime) { - acceptMime_ = mime; - } - - void SetUserAgent(const std::string &userAgent) { - userAgent_ = userAgent; - } - - void Start(); - - void Join(); - - // Returns 1.0 when done. That one value can be compared exactly - or just use Done(). - float Progress() const { return progress_.progress; } - float SpeedKBps() const { return progress_.kBps; } - - bool Done() const { return completed_; } - bool Failed() const { return failed_; } - - // NOTE! The value of ResultCode is INVALID until Done() returns true. - int ResultCode() const { return resultCode_; } - - std::string url() const { return url_; } - const Path &outfile() const { return outfile_; } - - // If not downloading to a file, access this to get the result. - Buffer &buffer() { return buffer_; } - const Buffer &buffer() const { return buffer_; } - - void Cancel() { - cancelled_ = true; - } - - bool IsCancelled() const { - return cancelled_; - } + virtual void SetAccept(const char *mime) = 0; + virtual void SetUserAgent(const std::string &userAgent) = 0; // NOTE: Completion callbacks (which these are) are deferred until RunCallback is called. This is so that // the call will end up on the thread that calls g_DownloadManager.Update(). @@ -154,6 +119,72 @@ public: } } + virtual void Start() = 0; + virtual void Join() = 0; + + virtual bool Done() const = 0; + virtual bool Failed() const = 0; + + virtual int ResultCode() const = 0; + + // Returns 1.0 when done. That one value can be compared exactly - or just use Done(). + float Progress() const { return progress_.progress; } + float SpeedKBps() const { return progress_.kBps; } + std::string url() const { return url_; } + virtual const Path &outfile() const = 0; + + virtual void Cancel() = 0; + virtual bool IsCancelled() const = 0; + + virtual Buffer &buffer() = 0; + virtual const Buffer &buffer() const = 0; + +protected: + std::function callback_; + std::string url_; + std::string name_; + net::RequestProgress progress_; + +private: +}; + +// Really an asynchronous request. +class HTTPDownload : public Download { +public: + HTTPDownload(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode = ProgressBarMode::DELAYED, const std::string &name = ""); + ~HTTPDownload(); + + void SetAccept(const char *mime) override { + acceptMime_ = mime; + } + + void SetUserAgent(const std::string &userAgent) override { + userAgent_ = userAgent; + } + + void Start() override; + void Join() override; + + bool Done() const override { return completed_; } + bool Failed() const override { return failed_; } + + // NOTE! The value of ResultCode is INVALID until Done() returns true. + int ResultCode() const override { return resultCode_; } + + const Path &outfile() const override { return outfile_; } + + // If not downloading to a file, access this to get the result. + Buffer &buffer() override { return buffer_; } + const Buffer &buffer() const override { return buffer_; } + + void Cancel() override { + cancelled_ = true; + } + + bool IsCancelled() const override { + return cancelled_; + } + private: void Do(); // Actually does the download. Runs on thread. int Perform(const std::string &url); @@ -161,12 +192,10 @@ private: void SetFailed(int code); RequestMethod method_; - net::RequestProgress progress_; std::string postData_; std::string userAgent_; Buffer buffer_; std::vector responseHeaders_; - std::string url_; Path outfile_; std::thread thread_; const char *acceptMime_ = "*/*"; @@ -177,8 +206,6 @@ private: bool cancelled_ = false; ProgressBarMode progressBarMode_; bool joined_ = false; - std::string name_; - std::function callback_; }; using std::shared_ptr; From f13a07f19bc0f5986963070808fe8e4e6e02dd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 20 Jul 2023 11:10:32 +0200 Subject: [PATCH 017/167] Break out Download and RequestManager into its own file --- CMakeLists.txt | 2 + Common/Common.vcxproj | 2 + Common/Common.vcxproj.filters | 8 +- Common/Net/HTTPClient.cpp | 82 ----------------- Common/Net/HTTPClient.h | 104 +-------------------- Common/Net/HTTPRequest.cpp | 89 ++++++++++++++++++ Common/Net/HTTPRequest.h | 117 ++++++++++++++++++++++++ UWP/CommonUWP/CommonUWP.vcxproj | 2 + UWP/CommonUWP/CommonUWP.vcxproj.filters | 6 ++ android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 11 files changed, 228 insertions(+), 186 deletions(-) create mode 100644 Common/Net/HTTPRequest.cpp create mode 100644 Common/Net/HTTPRequest.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dddcf33403..f11a7d71cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -710,6 +710,8 @@ add_library(Common STATIC Common/Net/HTTPClient.h Common/Net/HTTPHeaders.cpp Common/Net/HTTPHeaders.h + Common/Net/HTTPRequest.cpp + Common/Net/HTTPRequest.h Common/Net/HTTPServer.cpp Common/Net/HTTPServer.h Common/Net/NetBuffer.cpp diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 2e5a95a43b..c26c2295a3 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -494,6 +494,7 @@ + @@ -938,6 +939,7 @@ + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index deb16350c3..8c8aedeb5f 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -512,6 +512,9 @@ System + + Net + @@ -959,6 +962,9 @@ System + + Net + @@ -1107,4 +1113,4 @@ ext\basis_universal - \ No newline at end of file + diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index 9974fd6888..b5c47f6695 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -589,86 +589,4 @@ void HTTPDownload::Do() { completed_ = true; } -std::shared_ptr Downloader::StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime) { - std::shared_ptr dl(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode)); - - if (!userAgent_.empty()) - dl->SetUserAgent(userAgent_); - if (acceptMime) - dl->SetAccept(acceptMime); - newDownloads_.push_back(dl); - dl->Start(); - return dl; -} - -std::shared_ptr Downloader::StartDownloadWithCallback( - const std::string &url, - const Path &outfile, - ProgressBarMode mode, - std::function callback, - const std::string &name, - const char *acceptMime) { - std::shared_ptr dl(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode, name)); - if (!userAgent_.empty()) - dl->SetUserAgent(userAgent_); - if (acceptMime) - dl->SetAccept(acceptMime); - dl->SetCallback(callback); - newDownloads_.push_back(dl); - dl->Start(); - return dl; -} - -std::shared_ptr Downloader::AsyncPostWithCallback( - const std::string &url, - const std::string &postData, - const std::string &postMime, - ProgressBarMode mode, - std::function callback, - const std::string &name) { - std::shared_ptr dl(new HTTPDownload(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); - if (!userAgent_.empty()) - dl->SetUserAgent(userAgent_); - dl->SetCallback(callback); - newDownloads_.push_back(dl); - dl->Start(); - return dl; -} - -void Downloader::Update() { - for (auto iter : newDownloads_) { - downloads_.push_back(iter); - } - newDownloads_.clear(); - - restart: - for (size_t i = 0; i < downloads_.size(); i++) { - auto dl = downloads_[i]; - if (dl->Done()) { - dl->RunCallback(); - dl->Join(); - downloads_.erase(downloads_.begin() + i); - goto restart; - } - } -} - -void Downloader::WaitForAll() { - // TODO: Should lock? Though, OK if called from main thread, where Update() is called from. - while (!downloads_.empty()) { - Update(); - sleep_ms(10); - } -} - -void Downloader::CancelAll() { - for (size_t i = 0; i < downloads_.size(); i++) { - downloads_[i]->Cancel(); - } - for (size_t i = 0; i < downloads_.size(); i++) { - downloads_[i]->Join(); - } - downloads_.clear(); -} - } // http diff --git a/Common/Net/HTTPClient.h b/Common/Net/HTTPClient.h index 4235d0e13c..10113c3a9c 100644 --- a/Common/Net/HTTPClient.h +++ b/Common/Net/HTTPClient.h @@ -8,6 +8,7 @@ #include "Common/File/Path.h" #include "Common/Net/NetBuffer.h" #include "Common/Net/Resolve.h" +#include "Common/Net/HTTPRequest.h" namespace net { @@ -89,65 +90,6 @@ protected: double dataTimeout_ = 900.0; }; -enum class RequestMethod { - GET, - POST, -}; - -enum class ProgressBarMode { - NONE, - VISIBLE, - DELAYED, -}; - -class Download { -public: - Download(const std::string &url, const std::string &name, bool *cancelled) : url_(url), name_(name), progress_(cancelled) {} - virtual ~Download() {} - - virtual void SetAccept(const char *mime) = 0; - virtual void SetUserAgent(const std::string &userAgent) = 0; - - // NOTE: Completion callbacks (which these are) are deferred until RunCallback is called. This is so that - // the call will end up on the thread that calls g_DownloadManager.Update(). - void SetCallback(std::function callback) { - callback_ = callback; - } - void RunCallback() { - if (callback_) { - callback_(*this); - } - } - - virtual void Start() = 0; - virtual void Join() = 0; - - virtual bool Done() const = 0; - virtual bool Failed() const = 0; - - virtual int ResultCode() const = 0; - - // Returns 1.0 when done. That one value can be compared exactly - or just use Done(). - float Progress() const { return progress_.progress; } - float SpeedKBps() const { return progress_.kBps; } - std::string url() const { return url_; } - virtual const Path &outfile() const = 0; - - virtual void Cancel() = 0; - virtual bool IsCancelled() const = 0; - - virtual Buffer &buffer() = 0; - virtual const Buffer &buffer() const = 0; - -protected: - std::function callback_; - std::string url_; - std::string name_; - net::RequestProgress progress_; - -private: -}; - // Really an asynchronous request. class HTTPDownload : public Download { public: @@ -208,48 +150,4 @@ private: bool joined_ = false; }; -using std::shared_ptr; - -class Downloader { -public: - ~Downloader() { - CancelAll(); - } - - std::shared_ptr StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime = nullptr); - - std::shared_ptr StartDownloadWithCallback( - const std::string &url, - const Path &outfile, - ProgressBarMode mode, - std::function callback, - const std::string &name = "", - const char *acceptMime = nullptr); - - std::shared_ptr AsyncPostWithCallback( - const std::string &url, - const std::string &postData, - const std::string &postMime, // Use postMime = "application/x-www-form-urlencoded" for standard form-style posts, such as used by retroachievements. For encoding form data manually we have MultipartFormDataEncoder. - ProgressBarMode mode, - std::function callback, - const std::string &name = ""); - - // Drops finished downloads from the list. - void Update(); - void CancelAll(); - - void WaitForAll(); - void SetUserAgent(const std::string &userAgent) { - userAgent_ = userAgent; - } - -private: - std::vector> downloads_; - // These get copied to downloads_ in Update(). It's so that callbacks can add new downloads - // while running. - std::vector> newDownloads_; - - std::string userAgent_; -}; - } // http diff --git a/Common/Net/HTTPRequest.cpp b/Common/Net/HTTPRequest.cpp new file mode 100644 index 0000000000..c993bd4566 --- /dev/null +++ b/Common/Net/HTTPRequest.cpp @@ -0,0 +1,89 @@ +#include "Common/Net/HTTPRequest.h" +#include "Common/Net/HTTPClient.h" +#include "Common/TimeUtil.h" + +namespace http { + +std::shared_ptr RequestManager::StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime) { + std::shared_ptr dl(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode)); + + if (!userAgent_.empty()) + dl->SetUserAgent(userAgent_); + if (acceptMime) + dl->SetAccept(acceptMime); + newDownloads_.push_back(dl); + dl->Start(); + return dl; +} + +std::shared_ptr RequestManager::StartDownloadWithCallback( + const std::string &url, + const Path &outfile, + ProgressBarMode mode, + std::function callback, + const std::string &name, + const char *acceptMime) { + std::shared_ptr dl(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode, name)); + if (!userAgent_.empty()) + dl->SetUserAgent(userAgent_); + if (acceptMime) + dl->SetAccept(acceptMime); + dl->SetCallback(callback); + newDownloads_.push_back(dl); + dl->Start(); + return dl; +} + +std::shared_ptr RequestManager::AsyncPostWithCallback( + const std::string &url, + const std::string &postData, + const std::string &postMime, + ProgressBarMode mode, + std::function callback, + const std::string &name) { + std::shared_ptr dl(new HTTPDownload(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); + if (!userAgent_.empty()) + dl->SetUserAgent(userAgent_); + dl->SetCallback(callback); + newDownloads_.push_back(dl); + dl->Start(); + return dl; +} + +void RequestManager::Update() { + for (auto iter : newDownloads_) { + downloads_.push_back(iter); + } + newDownloads_.clear(); + +restart: + for (size_t i = 0; i < downloads_.size(); i++) { + auto dl = downloads_[i]; + if (dl->Done()) { + dl->RunCallback(); + dl->Join(); + downloads_.erase(downloads_.begin() + i); + goto restart; + } + } +} + +void RequestManager::WaitForAll() { + // TODO: Should lock? Though, OK if called from main thread, where Update() is called from. + while (!downloads_.empty()) { + Update(); + sleep_ms(10); + } +} + +void RequestManager::CancelAll() { + for (size_t i = 0; i < downloads_.size(); i++) { + downloads_[i]->Cancel(); + } + for (size_t i = 0; i < downloads_.size(); i++) { + downloads_[i]->Join(); + } + downloads_.clear(); +} + +} // namespace diff --git a/Common/Net/HTTPRequest.h b/Common/Net/HTTPRequest.h new file mode 100644 index 0000000000..ae09baa9ce --- /dev/null +++ b/Common/Net/HTTPRequest.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include + +#include "Common/File/Path.h" +#include "Common/Net/NetBuffer.h" + +namespace http { + +enum class RequestMethod { + GET, + POST, +}; + +enum class ProgressBarMode { + NONE, + VISIBLE, + DELAYED, +}; + +// Abstract request. +class Download { +public: + Download(const std::string &url, const std::string &name, bool *cancelled) : url_(url), name_(name), progress_(cancelled) {} + virtual ~Download() {} + + virtual void SetAccept(const char *mime) = 0; + virtual void SetUserAgent(const std::string &userAgent) = 0; + + // NOTE: Completion callbacks (which these are) are deferred until RunCallback is called. This is so that + // the call will end up on the thread that calls g_DownloadManager.Update(). + void SetCallback(std::function callback) { + callback_ = callback; + } + void RunCallback() { + if (callback_) { + callback_(*this); + } + } + + virtual void Start() = 0; + virtual void Join() = 0; + + virtual bool Done() const = 0; + virtual bool Failed() const = 0; + + virtual int ResultCode() const = 0; + + // Returns 1.0 when done. That one value can be compared exactly - or just use Done(). + float Progress() const { return progress_.progress; } + float SpeedKBps() const { return progress_.kBps; } + std::string url() const { return url_; } + virtual const Path &outfile() const = 0; + + virtual void Cancel() = 0; + virtual bool IsCancelled() const = 0; + + // Response + virtual Buffer &buffer() = 0; + virtual const Buffer &buffer() const = 0; + +protected: + std::function callback_; + std::string url_; + std::string name_; + net::RequestProgress progress_; + +private: +}; + +using std::shared_ptr; + +class RequestManager { +public: + ~RequestManager() { + CancelAll(); + } + + std::shared_ptr StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime = nullptr); + + std::shared_ptr StartDownloadWithCallback( + const std::string &url, + const Path &outfile, + ProgressBarMode mode, + std::function callback, + const std::string &name = "", + const char *acceptMime = nullptr); + + std::shared_ptr AsyncPostWithCallback( + const std::string &url, + const std::string &postData, + const std::string &postMime, // Use postMime = "application/x-www-form-urlencoded" for standard form-style posts, such as used by retroachievements. For encoding form data manually we have MultipartFormDataEncoder. + ProgressBarMode mode, + std::function callback, + const std::string &name = ""); + + // Drops finished downloads from the list. + void Update(); + void CancelAll(); + + void WaitForAll(); + void SetUserAgent(const std::string &userAgent) { + userAgent_ = userAgent; + } + +private: + std::vector> downloads_; + // These get copied to downloads_ in Update(). It's so that callbacks can add new downloads + // while running. + std::vector> newDownloads_; + + std::string userAgent_; +}; + +} // namespace net diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index e5a72c7d32..39bf0aece7 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -114,6 +114,7 @@ + @@ -273,6 +274,7 @@ + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index 2dfc1ae397..49b74be9b8 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -444,6 +444,9 @@ System + + Net + @@ -841,6 +844,9 @@ System + + Net + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index bfb6874e63..cffe34f588 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -238,6 +238,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Common/Math/lin/matrix4x4.cpp.arm \ $(SRC)/Common/Net/HTTPClient.cpp \ $(SRC)/Common/Net/HTTPHeaders.cpp \ + $(SRC)/Common/Net/HTTPRequest.cpp \ $(SRC)/Common/Net/HTTPServer.cpp \ $(SRC)/Common/Net/NetBuffer.cpp \ $(SRC)/Common/Net/Resolve.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 29bc7de7e4..bad6f6c73c 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -351,6 +351,7 @@ SOURCES_CXX += \ $(COMMONDIR)/Net/HTTPClient.cpp \ $(COMMONDIR)/Net/HTTPHeaders.cpp \ $(COMMONDIR)/Net/HTTPServer.cpp \ + $(COMMONDIR)/Net/HTTPRequest.cpp \ $(COMMONDIR)/Net/NetBuffer.cpp \ $(COMMONDIR)/Net/Resolve.cpp \ $(COMMONDIR)/Net/Sinks.cpp \ From 4a416174b111042910ee62024c016f227c340408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 20 Jul 2023 19:10:24 +0200 Subject: [PATCH 018/167] Avoid an assert for empty icon files in the store (should look into this more) --- UI/Store.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/UI/Store.cpp b/UI/Store.cpp index 0ad19143f6..0718ad2882 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -62,7 +62,11 @@ public: if (download.ResultCode() == 200) { std::string data; download.buffer().TakeAll(&data); - g_iconCache.InsertIcon(path_, IconFormat::PNG, std::move(data)); + if (!data.empty()) { + g_iconCache.InsertIcon(path_, IconFormat::PNG, std::move(data)); + } else { + g_iconCache.Cancel(path_); + } } else { g_iconCache.Cancel(path_); } From 7ae34f148a4f7f44798b44ce035c2c8f578b8771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 19 Jul 2023 00:32:56 +0200 Subject: [PATCH 019/167] Setup build for naett - a lightweight https wrapper --- .gitmodules | 3 +++ CMakeLists.txt | 2 +- Common/Common.vcxproj | 2 ++ Common/Common.vcxproj.filters | 9 +++++++++ android/jni/Android.mk | 6 +++++- ext/CMakeLists.txt | 1 + ext/naett | 1 + ext/naett-build/CMakeLists.txt | 10 ++++++++++ libretro/Makefile.common | 3 +++ 9 files changed, 35 insertions(+), 2 deletions(-) create mode 160000 ext/naett create mode 100644 ext/naett-build/CMakeLists.txt diff --git a/.gitmodules b/.gitmodules index 2ca84d342b..2dd1e55e80 100644 --- a/.gitmodules +++ b/.gitmodules @@ -47,3 +47,6 @@ [submodule "ext/rcheevos"] path = ext/rcheevos url = https://github.com/RetroAchievements/rcheevos.git +[submodule "ext/naett"] + path = ext/naett + url = https://github.com/erkkah/naett.git diff --git a/CMakeLists.txt b/CMakeLists.txt index f11a7d71cb..701f56b746 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2222,7 +2222,7 @@ else() include_directories(ext/zstd/lib) endif() -target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash rcheevos ${GlslangLibs} +target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash rcheevos naett ${GlslangLibs} ${CoreExtraLibs} ${OPENGL_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_DL_LIBS}) target_compile_features(${CoreLibName} PUBLIC cxx_std_17) diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index c26c2295a3..27910492ae 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -399,6 +399,7 @@ + @@ -848,6 +849,7 @@ + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 8c8aedeb5f..5653148c98 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -515,6 +515,9 @@ Net + + ext\naett + @@ -965,6 +968,9 @@ Net + + ext\naett + @@ -1075,6 +1081,9 @@ {d6d5f6e0-1c72-496b-af11-6d52d5123033} + + {34f45db9-5c08-49cb-b349-b9e760ce3213} + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index cffe34f588..1df74552df 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -79,7 +79,10 @@ SPIRV_CROSS_FILES := \ $(SRC)/ext/SPIRV-Cross/spirv_glsl.cpp \ $(SRC)/ext/SPIRV-Cross/spirv_parser.cpp \ $(SRC)/ext/SPIRV-Cross/spirv_cross_parsed_ir.cpp - + +NAETT_FILES := \ + ${SRC}/ext/naett/naett.c + RCHEEVOS_FILES := \ ${SRC}/ext/rcheevos/src/rapi/rc_api_common.c \ ${SRC}/ext/rcheevos/src/rapi/rc_api_editor.c \ @@ -178,6 +181,7 @@ EXEC_AND_LIB_FILES := \ $(VMA_FILES) \ $(SPIRV_CROSS_FILES) \ $(RCHEEVOS_FILES) \ + $(NAETT_FILES) \ $(EXT_FILES) \ $(NATIVE_FILES) \ $(SRC)/Common/Buffer.cpp \ diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index 8fc0581d45..d87af41ca4 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -31,6 +31,7 @@ add_subdirectory(snappy) add_subdirectory(udis86) add_subdirectory(SPIRV-Cross-build) add_subdirectory(rcheevos-build) +add_subdirectory(naett-build) if(USE_DISCORD AND NOT IOS AND NOT LIBRETRO) add_subdirectory(discord-rpc-build) endif() diff --git a/ext/naett b/ext/naett new file mode 160000 index 0000000000..cad8664cf2 --- /dev/null +++ b/ext/naett @@ -0,0 +1 @@ +Subproject commit cad8664cf236cdcf3df09c044ed0bf9e83d52555 diff --git a/ext/naett-build/CMakeLists.txt b/ext/naett-build/CMakeLists.txt new file mode 100644 index 0000000000..84d15860cc --- /dev/null +++ b/ext/naett-build/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required (VERSION 3.2.0) +project (naett) + +set(SRC_DIR ../naett/src) + +set(ALL_SOURCE_FILES + ${SRC_DIR}/naett/naett.c + ) + +add_library(naett STATIC ${ALL_SOURCE_FILES}) diff --git a/libretro/Makefile.common b/libretro/Makefile.common index bad6f6c73c..b94d6387d5 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -223,6 +223,9 @@ SOURCES_C += \ $(EXTDIR)/rcheevos/src/rhash/hash.c \ $(EXTDIR)/rcheevos/src/rhash/md5.c +SOURCES_C += + $(EXTDIR)/naett/naett.c + COREFLAGS += -DSTACK_LINE_READER_BUFFER_SIZE=1024 ifeq ($(PLATFORM_EXT), android) COREFLAGS += -DHAVE_DLFCN_H From e2cc835c2bb01d7152420a61933bfce46b4ba81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 20 Jul 2023 11:15:18 +0200 Subject: [PATCH 020/167] Setup build for new file HTTPNaettRequest --- CMakeLists.txt | 2 ++ Common/Common.vcxproj | 2 ++ Common/Common.vcxproj.filters | 8 +++++++- Common/Net/HTTPNaettRequest.cpp | 5 +++++ Common/Net/HTTPNaettRequest.h | 3 +++ UWP/CommonUWP/CommonUWP.vcxproj | 2 ++ UWP/CommonUWP/CommonUWP.vcxproj.filters | 6 ++++++ android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 9 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 Common/Net/HTTPNaettRequest.cpp create mode 100644 Common/Net/HTTPNaettRequest.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 701f56b746..dc5d33f33a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -710,6 +710,8 @@ add_library(Common STATIC Common/Net/HTTPClient.h Common/Net/HTTPHeaders.cpp Common/Net/HTTPHeaders.h + Common/Net/HTTPNaettRequest.cpp + Common/Net/HTTPNaettRequest.h Common/Net/HTTPRequest.cpp Common/Net/HTTPRequest.h Common/Net/HTTPServer.cpp diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 27910492ae..2b80aaa4d2 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -491,6 +491,7 @@ + @@ -937,6 +938,7 @@ + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 5653148c98..5527848423 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -518,6 +518,9 @@ ext\naett + + Net + @@ -971,6 +974,9 @@ ext\naett + + Net + @@ -1122,4 +1128,4 @@ ext\basis_universal - + \ No newline at end of file diff --git a/Common/Net/HTTPNaettRequest.cpp b/Common/Net/HTTPNaettRequest.cpp new file mode 100644 index 0000000000..0d1a373d4f --- /dev/null +++ b/Common/Net/HTTPNaettRequest.cpp @@ -0,0 +1,5 @@ +#include "Common/Net/HTTPRequest.h" +#include "Common/Net/HTTPNaettRequest.h" + +#include "ext/naett/naett.h" + diff --git a/Common/Net/HTTPNaettRequest.h b/Common/Net/HTTPNaettRequest.h new file mode 100644 index 0000000000..bb724b5c6e --- /dev/null +++ b/Common/Net/HTTPNaettRequest.h @@ -0,0 +1,3 @@ +#pragma once + +#include "Common/Net/HTTPRequest.h" diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index 39bf0aece7..8f5e2e715b 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -114,6 +114,7 @@ + @@ -274,6 +275,7 @@ + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index 49b74be9b8..8eb38864dd 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -447,6 +447,9 @@ Net + + Net + @@ -847,6 +850,9 @@ Net + + Net + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 1df74552df..4fe1d0df45 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -243,6 +243,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Common/Net/HTTPClient.cpp \ $(SRC)/Common/Net/HTTPHeaders.cpp \ $(SRC)/Common/Net/HTTPRequest.cpp \ + $(SRC)/Common/Net/HTTPNaettRequest.cpp \ $(SRC)/Common/Net/HTTPServer.cpp \ $(SRC)/Common/Net/NetBuffer.cpp \ $(SRC)/Common/Net/Resolve.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index b94d6387d5..f6bba72d5e 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -355,6 +355,7 @@ SOURCES_CXX += \ $(COMMONDIR)/Net/HTTPHeaders.cpp \ $(COMMONDIR)/Net/HTTPServer.cpp \ $(COMMONDIR)/Net/HTTPRequest.cpp \ + $(COMMONDIR)/Net/HTTPNaettRequest.cpp \ $(COMMONDIR)/Net/NetBuffer.cpp \ $(COMMONDIR)/Net/Resolve.cpp \ $(COMMONDIR)/Net/Sinks.cpp \ From 83e3cf787261a876a8bf3268f813d96e7d44b108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 20 Jul 2023 12:00:22 +0200 Subject: [PATCH 021/167] Add winhttp.lib dependency --- Windows/PPSSPP.vcxproj | 16 ++++++++-------- headless/Headless.vcxproj | 16 ++++++++-------- unittest/UnitTests.vcxproj | 16 ++++++++-------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index f345af5f1a..881a31cab5 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -249,7 +249,7 @@ stdcpp17 - uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/x86/lib true Windows @@ -289,7 +289,7 @@ stdcpp17 - uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/x86_64/lib true $(OutDir)$(ProjectName).pdb @@ -325,7 +325,7 @@ stdcpp17 - uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) + winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/aarch64/lib true $(OutDir)$(ProjectName).pdb @@ -359,7 +359,7 @@ stdcpp17 - uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) + winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/arm/lib true $(OutDir)$(ProjectName).pdb @@ -397,7 +397,7 @@ stdcpp17 - uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/x86/lib;%(AdditionalLibraryDirectories) $(OutDir)$(TargetName)$(TargetExt) true @@ -446,7 +446,7 @@ stdcpp17 - uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/x86_64/lib;%(AdditionalLibraryDirectories) true Windows @@ -489,7 +489,7 @@ stdcpp17 - uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) + winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/aarch64/lib;%(AdditionalLibraryDirectories) true Windows @@ -530,7 +530,7 @@ stdcpp17 - uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) + winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies) ../ffmpeg/Windows/arm/lib;%(AdditionalLibraryDirectories) true Windows diff --git a/headless/Headless.vcxproj b/headless/Headless.vcxproj index 695f20dbe4..65cc85d5b4 100644 --- a/headless/Headless.vcxproj +++ b/headless/Headless.vcxproj @@ -191,7 +191,7 @@ Console true - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;d3dx9d.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + winhttp.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;d3dx9d.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) 0x00400000 false true @@ -225,7 +225,7 @@ Console true - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;d3dx9d.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + winhttp.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;d3dx9d.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) 0x00400000 false true @@ -258,7 +258,7 @@ Console true - ole32.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;shell32.lib;advapi32.lib;gdi32.lib;%(AdditionalDependencies) + winhttp.lib;ole32.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;shell32.lib;advapi32.lib;gdi32.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ../ffmpeg/Windows/aarch64/lib @@ -289,7 +289,7 @@ Console true - ole32.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;shell32.lib;advapi32.lib;gdi32.lib;%(AdditionalDependencies) + winhttp.lib;ole32.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;shell32.lib;advapi32.lib;gdi32.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ../ffmpeg/Windows/arm/lib @@ -323,7 +323,7 @@ true true true - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;d3dx9.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + winhttp.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;d3dx9.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) 0x00400000 false true @@ -362,7 +362,7 @@ true true true - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;d3dx9.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + winhttp.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;d3dx9.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) 0x00400000 false true @@ -400,7 +400,7 @@ true true true - ole32.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;shell32.lib;advapi32.lib;gdi32.lib;%(AdditionalDependencies) + winhttp.lib;ole32.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;shell32.lib;advapi32.lib;gdi32.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ../ffmpeg/Windows/aarch64/lib;%(AdditionalLibraryDirectories) @@ -435,7 +435,7 @@ true true true - ole32.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;shell32.lib;advapi32.lib;gdi32.lib;%(AdditionalDependencies) + winhttp.lib;ole32.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;shell32.lib;advapi32.lib;gdi32.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ../ffmpeg/Windows/arm/lib;%(AdditionalLibraryDirectories) diff --git a/unittest/UnitTests.vcxproj b/unittest/UnitTests.vcxproj index e96bfd7892..7417f4f38b 100644 --- a/unittest/UnitTests.vcxproj +++ b/unittest/UnitTests.vcxproj @@ -181,7 +181,7 @@ Console true - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + winhttp.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ../ffmpeg/Windows/x86/lib @@ -205,7 +205,7 @@ Console true - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + winhttp.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ../ffmpeg/Windows/x86_64/lib @@ -229,7 +229,7 @@ Console true - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) + winhttp.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ../ffmpeg/Windows/aarch64/lib @@ -254,7 +254,7 @@ Console true - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) + winhttp.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ../ffmpeg/Windows/arm/lib @@ -283,7 +283,7 @@ true true true - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + winhttp.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ../ffmpeg/Windows/x86/lib @@ -313,7 +313,7 @@ true true true - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) + winhttp.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ../ffmpeg/Windows/x86_64/lib @@ -343,7 +343,7 @@ true true true - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) + winhttp.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ../ffmpeg/Windows/aarch64/lib @@ -373,7 +373,7 @@ true true true - mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) + winhttp.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ../ffmpeg/Windows/arm/lib From 010c58231b2fb99c5ff11cf1b8a039d95906745d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 20 Jul 2023 13:50:10 +0200 Subject: [PATCH 022/167] Fix cmake build --- ext/naett-build/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/naett-build/CMakeLists.txt b/ext/naett-build/CMakeLists.txt index 84d15860cc..6b3b489346 100644 --- a/ext/naett-build/CMakeLists.txt +++ b/ext/naett-build/CMakeLists.txt @@ -1,10 +1,10 @@ cmake_minimum_required (VERSION 3.2.0) project (naett) -set(SRC_DIR ../naett/src) +set(SRC_DIR ../naett) set(ALL_SOURCE_FILES - ${SRC_DIR}/naett/naett.c + ${SRC_DIR}/naett.c ) add_library(naett STATIC ${ALL_SOURCE_FILES}) From fbd980bee62160c7e93967c79ae91007b8fc6296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 20 Jul 2023 11:25:27 +0200 Subject: [PATCH 023/167] Get basic Naett requests to work (the store works in https mode) --- Common/Net/HTTPClient.cpp | 3 +- Common/Net/HTTPClient.h | 2 +- Common/Net/HTTPNaettRequest.cpp | 87 +++++++++++++++++++++++++++++++++ Common/Net/HTTPNaettRequest.h | 65 ++++++++++++++++++++++++ Common/Net/HTTPRequest.cpp | 27 ++++++++-- Common/Net/HTTPRequest.h | 4 +- Common/Net/Resolve.cpp | 7 +++ Common/System/System.h | 2 + Core/Config.cpp | 4 +- Core/Config.h | 4 +- UI/Store.cpp | 12 ++--- Windows/main.cpp | 2 + 12 files changed, 202 insertions(+), 17 deletions(-) diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index b5c47f6695..5638e30529 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -542,10 +542,9 @@ std::string HTTPDownload::RedirectLocation(const std::string &baseUrl) { } void HTTPDownload::Do() { - SetCurrentThreadName("Downloader::Do"); + SetCurrentThreadName("HTTPDownload::Do"); AndroidJNIThreadContext jniContext; - resultCode_ = 0; std::string downloadURL = url_; diff --git a/Common/Net/HTTPClient.h b/Common/Net/HTTPClient.h index 10113c3a9c..85543bb53c 100644 --- a/Common/Net/HTTPClient.h +++ b/Common/Net/HTTPClient.h @@ -107,7 +107,7 @@ public: void Start() override; void Join() override; - bool Done() const override { return completed_; } + bool Done() override { return completed_; } bool Failed() const override { return failed_; } // NOTE! The value of ResultCode is INVALID until Done() returns true. diff --git a/Common/Net/HTTPNaettRequest.cpp b/Common/Net/HTTPNaettRequest.cpp index 0d1a373d4f..dd0539cc39 100644 --- a/Common/Net/HTTPNaettRequest.cpp +++ b/Common/Net/HTTPNaettRequest.cpp @@ -1,5 +1,92 @@ +#include + #include "Common/Net/HTTPRequest.h" #include "Common/Net/HTTPNaettRequest.h" +#include "Common/Thread/ThreadUtil.h" +#include "Common/Log.h" #include "ext/naett/naett.h" +namespace http { + +HTTPSDownload::HTTPSDownload(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode, const std::string &name) + : Download(url, name, progress_.cancelled), method_(method), postData_(postData), postMime_(postMime), outfile_(outfile), progressBarMode_(progressBarMode) { +} + +HTTPSDownload::~HTTPSDownload() { + Join(); +} + +void HTTPSDownload::Start() { + _dbg_assert_(!req_); + + const char *methodStr = method_ == RequestMethod::GET ? "GET" : "POST"; + req_ = naettRequest_va(url_.c_str(), !postMime_.empty() ? 3 : 2, naettMethod(methodStr), naettHeader("accept", "application/json, text/*; q=0.9, */*; q=0.8"), naettHeader("Content-Type", postMime_.c_str())); + res_ = naettMake(req_); +} + +void HTTPSDownload::Join() { + if (!res_ || !req_) + return; // No pending operation. + // Tear down. + if (completed_ && res_) { + _dbg_assert_(req_); + naettClose(res_); + naettFree(req_); + res_ = nullptr; + req_ = nullptr; + } else { + ERROR_LOG(IO, "HTTPSDownload::Join not implemented"); + } +} + +bool HTTPSDownload::Done() { + if (completed_) + return true; + + if (naettComplete(res_)) { + resultCode_ = naettGetStatus(res_); + if (resultCode_ < 0) { + // It's a naett error. Translate and handle. + switch (resultCode_) { + case naettConnectionError: // -1 + ERROR_LOG(IO, "Connection error"); + break; + case naettProtocolError: // -2 + ERROR_LOG(IO, "Protocol error"); + break; + case naettReadError: // -3 + ERROR_LOG(IO, "Read error"); + break; + case naettWriteError: // -4 + ERROR_LOG(IO, "Write error"); + break; + case naettGenericError: // -4 + ERROR_LOG(IO, "Generic error"); + break; + } + } else if (resultCode_ == 200) { + int bodyLength; + const void *body = naettGetBody(res_, &bodyLength); + char *dest = buffer_.Append(bodyLength); + memcpy(dest, body, bodyLength); + + if (!outfile_.empty() && !buffer_.FlushToFile(outfile_)) { + ERROR_LOG(IO, "Failed writing download to '%s'", outfile_.c_str()); + } + } else { + WARN_LOG(IO, "Naett request failed: %d", resultCode_); + failed_ = true; + } + + completed_ = true; + + if (callback_) + callback_(*this); + return true; + } + + return false; +} + +} // namespace diff --git a/Common/Net/HTTPNaettRequest.h b/Common/Net/HTTPNaettRequest.h index bb724b5c6e..3dd557086e 100644 --- a/Common/Net/HTTPNaettRequest.h +++ b/Common/Net/HTTPNaettRequest.h @@ -1,3 +1,68 @@ #pragma once #include "Common/Net/HTTPRequest.h" +#include "ext/naett/naett.h" +#include + +namespace http { + +// Really an asynchronous request. +class HTTPSDownload : public Download { +public: + HTTPSDownload(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode = ProgressBarMode::DELAYED, const std::string &name = ""); + ~HTTPSDownload(); + + void SetAccept(const char *mime) override { + acceptMime_ = mime; + } + + void SetUserAgent(const std::string &userAgent) override { + userAgent_ = userAgent; + } + + void Start() override; + void Join() override; + + // Also acts as a Poll. + bool Done() override; + bool Failed() const override { return failed_; } + + // NOTE! The value of ResultCode is INVALID until Done() returns true. + int ResultCode() const override { return resultCode_; } + + const Path &outfile() const override { return outfile_; } + + // If not downloading to a file, access this to get the result. + Buffer &buffer() override { return buffer_; } + const Buffer &buffer() const override { return buffer_; } + + void Cancel() override { + cancelled_ = true; + } + + bool IsCancelled() const override { + return cancelled_; + } + +private: + RequestMethod method_; + std::string postData_; + std::string userAgent_; + Buffer buffer_; + std::vector responseHeaders_; + Path outfile_; + const char *acceptMime_ = "*/*"; + std::string postMime_; + int resultCode_ = 0; + bool completed_ = false; + bool failed_ = false; + bool cancelled_ = false; + ProgressBarMode progressBarMode_; + bool joined_ = false; + + // Naett state + naettReq *req_ = nullptr; + naettRes *res_ = nullptr; +}; + +} // namespace http diff --git a/Common/Net/HTTPRequest.cpp b/Common/Net/HTTPRequest.cpp index c993bd4566..988f81b190 100644 --- a/Common/Net/HTTPRequest.cpp +++ b/Common/Net/HTTPRequest.cpp @@ -1,11 +1,22 @@ #include "Common/Net/HTTPRequest.h" #include "Common/Net/HTTPClient.h" +#include "Common/Net/HTTPNaettRequest.h" #include "Common/TimeUtil.h" +#include "Common/StringUtils.h" namespace http { +bool RequestManager::IsHttpsUrl(const std::string &url) { + return startsWith(url, "https:"); +} + std::shared_ptr RequestManager::StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime) { - std::shared_ptr dl(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode)); + std::shared_ptr dl; + if (IsHttpsUrl(url)) { + dl.reset(new HTTPSDownload(RequestMethod::GET, url, "", "", outfile, mode)); + } else { + dl.reset(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode)); + } if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); @@ -23,7 +34,12 @@ std::shared_ptr RequestManager::StartDownloadWithCallback( std::function callback, const std::string &name, const char *acceptMime) { - std::shared_ptr dl(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode, name)); + std::shared_ptr dl; + if (IsHttpsUrl(url)) { + dl.reset(new HTTPSDownload(RequestMethod::GET, url, "", "", outfile, mode, name)); + } else { + dl.reset(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode, name)); + } if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); if (acceptMime) @@ -41,7 +57,12 @@ std::shared_ptr RequestManager::AsyncPostWithCallback( ProgressBarMode mode, std::function callback, const std::string &name) { - std::shared_ptr dl(new HTTPDownload(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); + std::shared_ptr dl; + if (IsHttpsUrl(url)) { + dl.reset(new HTTPSDownload(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); + } else { + dl.reset(new HTTPDownload(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); + } if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); dl->SetCallback(callback); diff --git a/Common/Net/HTTPRequest.h b/Common/Net/HTTPRequest.h index ae09baa9ce..7c55c5bd85 100644 --- a/Common/Net/HTTPRequest.h +++ b/Common/Net/HTTPRequest.h @@ -43,7 +43,7 @@ public: virtual void Start() = 0; virtual void Join() = 0; - virtual bool Done() const = 0; + virtual bool Done() = 0; virtual bool Failed() const = 0; virtual int ResultCode() const = 0; @@ -106,6 +106,8 @@ public: } private: + bool IsHttpsUrl(const std::string &url); + std::vector> downloads_; // These get copied to downloads_ in Update(). It's so that callbacks can add new downloads // while running. diff --git a/Common/Net/Resolve.cpp b/Common/Net/Resolve.cpp index 76f0055f50..441905777d 100644 --- a/Common/Net/Resolve.cpp +++ b/Common/Net/Resolve.cpp @@ -30,8 +30,11 @@ #include "Common/Log.h" #include "Common/TimeUtil.h" +#include "ext/naett/naett.h" + namespace net { +int g_refCount = 0; void Init() { @@ -40,6 +43,10 @@ void Init() WSADATA wsaData = {0}; WSAStartup(MAKEWORD(2, 2), &wsaData); #endif + if (g_refCount == 0) { + naettInit(NULL); + } + g_refCount++; } void Shutdown() diff --git a/Common/System/System.h b/Common/System/System.h index fd1ca445a5..17eeda194d 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -136,6 +136,8 @@ enum SystemProperty { SYSPROP_CAN_CREATE_SHORTCUT, + SYSPROP_SUPPORTS_HTTPS, + // Available as Int: SYSPROP_SYSTEMVERSION, SYSPROP_DISPLAY_XRES, diff --git a/Core/Config.cpp b/Core/Config.cpp index 316157d3e5..70eb254f2e 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -57,7 +57,7 @@ #include "GPU/Common/FramebufferManagerCommon.h" // TODO: Find a better place for this. -http::Downloader g_DownloadManager; +http::RequestManager g_DownloadManager; Config g_Config; @@ -1174,7 +1174,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { if (iRunCount % 10 == 0 && bCheckForNewVersion) { const char *versionUrl = "http://www.ppsspp.org/version.json"; const char *acceptMime = "application/json, text/*; q=0.9, */*; q=0.8"; - g_DownloadManager.StartDownloadWithCallback(versionUrl, Path(), http::ProgressBarMode::NONE, &DownloadCompletedCallback, acceptMime); + g_DownloadManager.StartDownloadWithCallback(versionUrl, Path(), http::ProgressBarMode::NONE, &DownloadCompletedCallback, "version", acceptMime); } INFO_LOG(LOADER, "Loading controller config: %s", controllerIniFilename_.c_str()); diff --git a/Core/Config.h b/Core/Config.h index bd1b4223a1..98edbd35b5 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -42,7 +42,7 @@ enum ChatPositions { namespace http { class Download; - class Downloader; + class RequestManager; } struct UrlEncoder; @@ -595,6 +595,6 @@ private: std::string CreateRandMAC(); // TODO: Find a better place for this. -extern http::Downloader g_DownloadManager; +extern http::RequestManager g_DownloadManager; extern Config g_Config; diff --git a/UI/Store.cpp b/UI/Store.cpp index 0718ad2882..b8fe72bc2a 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -35,7 +35,7 @@ #include "UI/EmuScreen.h" #include "UI/Store.h" -const std::string storeBaseUrl = "http://store.ppsspp.org/"; +const std::string storeBaseUrl = "https://store.ppsspp.org/"; // baseUrl is assumed to have a trailing slash, and not contain any subdirectories. std::string ResolveUrl(std::string baseUrl, std::string url) { @@ -53,12 +53,12 @@ std::string ResolveUrl(std::string baseUrl, std::string url) { class HttpImageFileView : public UI::View { public: - HttpImageFileView(http::Downloader *downloader, const std::string &path, UI::ImageSizeMode sizeMode = UI::IS_DEFAULT, bool useIconCache = true, UI::LayoutParams *layoutParams = nullptr) - : UI::View(layoutParams), path_(path), sizeMode_(sizeMode), downloader_(downloader), useIconCache_(useIconCache) { + HttpImageFileView(http::RequestManager *requestManager, const std::string &path, UI::ImageSizeMode sizeMode = UI::IS_DEFAULT, bool useIconCache = true, UI::LayoutParams *layoutParams = nullptr) + : UI::View(layoutParams), path_(path), sizeMode_(sizeMode), requestManager_(requestManager), useIconCache_(useIconCache) { if (useIconCache && g_iconCache.MarkPending(path_)) { const char *acceptMime = "image/png, image/jpeg, image/*; q=0.9, */*; q=0.8"; - downloader_->StartDownloadWithCallback(path_, Path(), http::ProgressBarMode::DELAYED, [&](http::Download &download) { + requestManager_->StartDownloadWithCallback(path_, Path(), http::ProgressBarMode::DELAYED, [&](http::Download &download) { if (download.ResultCode() == 200) { std::string data; download.buffer().TakeAll(&data); @@ -100,7 +100,7 @@ private: std::string path_; // or cache key uint32_t color_ = 0xFFFFFFFF; UI::ImageSizeMode sizeMode_; - http::Downloader *downloader_; + http::RequestManager *requestManager_; std::shared_ptr download_; std::string textureData_; @@ -169,7 +169,7 @@ void HttpImageFileView::Draw(UIContext &dc) { if (!texture_ && !textureFailed_ && !path_.empty() && !download_) { auto cb = std::bind(&HttpImageFileView::DownloadCompletedCallback, this, std::placeholders::_1); const char *acceptMime = "image/png, image/jpeg, image/*; q=0.9, */*; q=0.8"; - downloader_->StartDownloadWithCallback(path_, Path(), http::ProgressBarMode::NONE, cb, acceptMime); + requestManager_->StartDownloadWithCallback(path_, Path(), http::ProgressBarMode::NONE, cb, acceptMime); } if (!textureData_.empty()) { diff --git a/Windows/main.cpp b/Windows/main.cpp index 7fe8de2ddc..6cbd2e6243 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -375,6 +375,8 @@ bool System_GetPropertyBool(SystemProperty prop) { return true; case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR: return true; // FileUtil.cpp: OpenFileInEditor + case SYSPROP_SUPPORTS_HTTPS: + return true; default: return false; } From ab6e902fea30f07e6a9e774c9e1bc92ae8c7a27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 20 Jul 2023 16:01:51 +0200 Subject: [PATCH 024/167] Make naett work on Android, UWP, Mac. Exclude on Linux --- CMakeLists.txt | 16 +++++++++++++++- Common/Net/HTTPNaettRequest.cpp | 6 +++++- Common/Net/HTTPNaettRequest.h | 10 ++++++++-- Common/Net/HTTPRequest.cpp | 12 ++++++++++++ Common/Net/Resolve.cpp | 20 +++++++++++++++++--- Common/Render/Text/draw_text_android.cpp | 1 + Common/System/Request.cpp | 13 +++++++++++++ Common/UI/IconCache.cpp | 1 + Core/Util/PortManager.cpp | 1 + SDL/SDLMain.cpp | 4 ++++ UWP/CommonUWP/CommonUWP.vcxproj | 2 ++ UWP/CommonUWP/CommonUWP.vcxproj.filters | 9 +++++++++ android/jni/app-android.cpp | 7 ++++++- ext/CMakeLists.txt | 4 +++- ext/naett | 2 +- ios/main.mm | 5 ++++- libretro/Makefile.common | 5 ++--- 17 files changed, 104 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dc5d33f33a..452b74f1a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -219,6 +219,16 @@ else() set(CoreLinkType STATIC) endif() +if(NOT ANDROID AND NOT WIN32 AND NOT APPLE) + set(HTTPS_NOT_AVAILABLE ON) +endif() + +# Made this flag negative because it's hopefully quite temporary and didn't +# want to have to update all build systems. +if(HTTPS_NOT_AVAILABLE) + add_definitions(-DHTTPS_NOT_AVAILABLE) +endif() + # Work around for some misfeature of the current glslang build system include_directories(ext/glslang) @@ -2224,9 +2234,13 @@ else() include_directories(ext/zstd/lib) endif() -target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash rcheevos naett ${GlslangLibs} +target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash rcheevos ${GlslangLibs} ${CoreExtraLibs} ${OPENGL_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_DL_LIBS}) +if(NOT HTTPS_NOT_AVAILABLE) + target_link_libraries(${CoreLibName} naett) +endif() + target_compile_features(${CoreLibName} PUBLIC cxx_std_17) if(FFmpeg_FOUND) diff --git a/Common/Net/HTTPNaettRequest.cpp b/Common/Net/HTTPNaettRequest.cpp index dd0539cc39..4fce721925 100644 --- a/Common/Net/HTTPNaettRequest.cpp +++ b/Common/Net/HTTPNaettRequest.cpp @@ -1,3 +1,5 @@ +#ifndef HTTPS_NOT_AVAILABLE + #include #include "Common/Net/HTTPRequest.h" @@ -10,7 +12,7 @@ namespace http { HTTPSDownload::HTTPSDownload(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode, const std::string &name) - : Download(url, name, progress_.cancelled), method_(method), postData_(postData), postMime_(postMime), outfile_(outfile), progressBarMode_(progressBarMode) { + : Download(url, name, &cancelled_), method_(method), postData_(postData), postMime_(postMime), outfile_(outfile), progressBarMode_(progressBarMode) { } HTTPSDownload::~HTTPSDownload() { @@ -90,3 +92,5 @@ bool HTTPSDownload::Done() { } } // namespace + +#endif // HTTPS_NOT_AVAILABLE diff --git a/Common/Net/HTTPNaettRequest.h b/Common/Net/HTTPNaettRequest.h index 3dd557086e..6a9f62eba9 100644 --- a/Common/Net/HTTPNaettRequest.h +++ b/Common/Net/HTTPNaettRequest.h @@ -1,9 +1,13 @@ #pragma once -#include "Common/Net/HTTPRequest.h" -#include "ext/naett/naett.h" #include +#include "Common/Net/HTTPRequest.h" + +#ifndef HTTPS_NOT_AVAILABLE + +#include "ext/naett/naett.h" + namespace http { // Really an asynchronous request. @@ -66,3 +70,5 @@ private: }; } // namespace http + +#endif // HTTPS_NOT_AVAILABLE diff --git a/Common/Net/HTTPRequest.cpp b/Common/Net/HTTPRequest.cpp index 988f81b190..318470a09d 100644 --- a/Common/Net/HTTPRequest.cpp +++ b/Common/Net/HTTPRequest.cpp @@ -13,7 +13,11 @@ bool RequestManager::IsHttpsUrl(const std::string &url) { std::shared_ptr RequestManager::StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime) { std::shared_ptr dl; if (IsHttpsUrl(url)) { +#ifndef HTTPS_NOT_AVAILABLE dl.reset(new HTTPSDownload(RequestMethod::GET, url, "", "", outfile, mode)); +#else + return std::shared_ptr(); +#endif } else { dl.reset(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode)); } @@ -36,7 +40,11 @@ std::shared_ptr RequestManager::StartDownloadWithCallback( const char *acceptMime) { std::shared_ptr dl; if (IsHttpsUrl(url)) { +#ifndef HTTPS_NOT_AVAILABLE dl.reset(new HTTPSDownload(RequestMethod::GET, url, "", "", outfile, mode, name)); +#else + return std::shared_ptr(); +#endif } else { dl.reset(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode, name)); } @@ -59,7 +67,11 @@ std::shared_ptr RequestManager::AsyncPostWithCallback( const std::string &name) { std::shared_ptr dl; if (IsHttpsUrl(url)) { +#ifndef HTTPS_NOT_AVAILABLE dl.reset(new HTTPSDownload(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); +#else + return std::shared_ptr(); +#endif } else { dl.reset(new HTTPDownload(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); } diff --git a/Common/Net/Resolve.cpp b/Common/Net/Resolve.cpp index 441905777d..2438fc5b33 100644 --- a/Common/Net/Resolve.cpp +++ b/Common/Net/Resolve.cpp @@ -30,11 +30,18 @@ #include "Common/Log.h" #include "Common/TimeUtil.h" +#ifndef HTTPS_NOT_AVAILABLE #include "ext/naett/naett.h" +#endif + +#if PPSSPP_PLATFORM(ANDROID) +#include +extern JavaVM *gJvm; +#endif namespace net { -int g_refCount = 0; +static bool g_naettInitialized; void Init() { @@ -43,10 +50,17 @@ void Init() WSADATA wsaData = {0}; WSAStartup(MAKEWORD(2, 2), &wsaData); #endif - if (g_refCount == 0) { + if (!g_naettInitialized) { +#ifndef HTTPS_NOT_AVAILABLE +#if PPSSPP_PLATFORM(ANDROID) + _assert_(gJvm != nullptr); + naettInit(gJvm); +#else naettInit(NULL); +#endif +#endif + g_naettInitialized = true; } - g_refCount++; } void Shutdown() diff --git a/Common/Render/Text/draw_text_android.cpp b/Common/Render/Text/draw_text_android.cpp index cab0bdca7e..d1e6244cb2 100644 --- a/Common/Render/Text/draw_text_android.cpp +++ b/Common/Render/Text/draw_text_android.cpp @@ -1,4 +1,5 @@ #include "ppsspp_config.h" + #include "Common/Log.h" #include "Common/StringUtils.h" #include "Common/System/Display.h" diff --git a/Common/System/Request.cpp b/Common/System/Request.cpp index dbfcff7c62..8e78352908 100644 --- a/Common/System/Request.cpp +++ b/Common/System/Request.cpp @@ -1,3 +1,5 @@ +#include "ppsspp_config.h" + #include #include "Common/System/Request.h" @@ -6,6 +8,17 @@ #include "Common/File/Path.h" #include "Common/TimeUtil.h" +#if PPSSPP_PLATFORM(ANDROID) + +// Maybe not the most natural place for this, but not sure what would be. It needs to be in the Common project +// unless we want to make another System_ function to retrieve it. + +#include + +JavaVM *gJvm = nullptr; + +#endif + RequestManager g_requestManager; const char *RequestTypeAsString(SystemRequestType type) { diff --git a/Common/UI/IconCache.cpp b/Common/UI/IconCache.cpp index f5b28881a0..830655e3df 100644 --- a/Common/UI/IconCache.cpp +++ b/Common/UI/IconCache.cpp @@ -239,6 +239,7 @@ bool IconCache::InsertIcon(const std::string &key, IconFormat format, std::strin } if (data.empty()) { + _dbg_assert_(false); ERROR_LOG(G3D, "Can't insert empty data into icon cache"); return false; } diff --git a/Core/Util/PortManager.cpp b/Core/Util/PortManager.cpp index 2e70d89233..d4103e0e75 100644 --- a/Core/Util/PortManager.cpp +++ b/Core/Util/PortManager.cpp @@ -54,6 +54,7 @@ PortManager::PortManager(): m_InitState(UPNP_INITSTATE_NONE), m_LocalPort(UPNP_LOCAL_PORT_ANY), m_leaseDuration("43200") { + // Don't call net::Init or similar here, we don't want stuff like that to happen before main. } PortManager::~PortManager() { diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index f46af287ad..955592a492 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -529,6 +529,10 @@ bool System_GetPropertyBool(SystemProperty prop) { return true; case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR: return true; // FileUtil.cpp: OpenFileInEditor +#ifndef HTTPS_NOT_AVAILABLE + case SYSPROP_SUPPORTS_HTTPS: + return true; +#endif #if PPSSPP_PLATFORM(MAC) case SYSPROP_HAS_FOLDER_BROWSER: case SYSPROP_HAS_FILE_BROWSER: diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index 8f5e2e715b..3e9878bf65 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -261,6 +261,7 @@ + @@ -402,6 +403,7 @@ + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index 8eb38864dd..2eedf9e8c6 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -94,6 +94,9 @@ {8b92cc15-8e3e-45b1-ba80-ad39b1a9cdc8} + + {403c3154-5e8a-4da1-ba82-8f69bef25e14} + @@ -450,6 +453,9 @@ Net + + ext\naett + @@ -853,6 +859,9 @@ Net + + ext\naett + diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index ca80a429ba..61c737396c 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -158,7 +158,8 @@ static int desiredBackbufferSizeX; static int desiredBackbufferSizeY; // Cache the class loader so we can use it from native threads. Required for TextAndroid. -static JavaVM* gJvm = nullptr; +extern JavaVM *gJvm; + static jobject gClassLoader; static jmethodID gFindClassMethod; @@ -534,6 +535,10 @@ bool System_GetPropertyBool(SystemProperty prop) { } case SYSPROP_HAS_KEYBOARD: return deviceType != DEVICE_TYPE_VR; +#ifndef HTTPS_NOT_AVAILABLE + case SYSPROP_SUPPORTS_HTTPS: + return true; +#endif default: return false; } diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index d87af41ca4..9bbb9b7a74 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -31,7 +31,9 @@ add_subdirectory(snappy) add_subdirectory(udis86) add_subdirectory(SPIRV-Cross-build) add_subdirectory(rcheevos-build) -add_subdirectory(naett-build) +if(NOT HTTPS_NOT_AVAILABLE) + add_subdirectory(naett-build) +endif() if(USE_DISCORD AND NOT IOS AND NOT LIBRETRO) add_subdirectory(discord-rpc-build) endif() diff --git a/ext/naett b/ext/naett index cad8664cf2..6206522f33 160000 --- a/ext/naett +++ b/ext/naett @@ -1 +1 @@ -Subproject commit cad8664cf236cdcf3df09c044ed0bf9e83d52555 +Subproject commit 6206522f33e2bc49fcd0c2063ff8b4e9734416c9 diff --git a/ios/main.mm b/ios/main.mm index 18d23f8517..4ca2f041dd 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -168,7 +168,10 @@ bool System_GetPropertyBool(SystemProperty prop) { #endif case SYSPROP_CAN_JIT: return get_debugged(); - +#ifndef HTTPS_NOT_AVAILABLE + case SYSPROP_SUPPORTS_HTTPS: + return true; +#endif default: return false; } diff --git a/libretro/Makefile.common b/libretro/Makefile.common index f6bba72d5e..8c6a90ca06 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -223,10 +223,9 @@ SOURCES_C += \ $(EXTDIR)/rcheevos/src/rhash/hash.c \ $(EXTDIR)/rcheevos/src/rhash/md5.c -SOURCES_C += - $(EXTDIR)/naett/naett.c - COREFLAGS += -DSTACK_LINE_READER_BUFFER_SIZE=1024 +COREFLAGS += -DHTTPS_NOT_AVAILABLE + ifeq ($(PLATFORM_EXT), android) COREFLAGS += -DHAVE_DLFCN_H else ifneq ($(PLATFORM_EXT), win32) From 96a75f9fc47c35fae824af1c62e07740b0be9503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 21 Jul 2023 10:06:04 +0200 Subject: [PATCH 025/167] naett: Set timeout, POST body, etc. Just missing user agent --- Common/Net/HTTPNaettRequest.cpp | 111 +++++++++++++++++++------------- 1 file changed, 67 insertions(+), 44 deletions(-) diff --git a/Common/Net/HTTPNaettRequest.cpp b/Common/Net/HTTPNaettRequest.cpp index 4fce721925..c56295aae4 100644 --- a/Common/Net/HTTPNaettRequest.cpp +++ b/Common/Net/HTTPNaettRequest.cpp @@ -21,9 +21,28 @@ HTTPSDownload::~HTTPSDownload() { void HTTPSDownload::Start() { _dbg_assert_(!req_); + _dbg_assert_(!res_); - const char *methodStr = method_ == RequestMethod::GET ? "GET" : "POST"; - req_ = naettRequest_va(url_.c_str(), !postMime_.empty() ? 3 : 2, naettMethod(methodStr), naettHeader("accept", "application/json, text/*; q=0.9, */*; q=0.8"), naettHeader("Content-Type", postMime_.c_str())); + std::vector options; + options.push_back(naettMethod(method_ == RequestMethod::GET ? "GET" : "POST")); + options.push_back(naettHeader("Accept", acceptMime_)); + if (!postMime_.empty()) { + options.push_back(naettHeader("Content-Type", postMime_.c_str())); + } + if (method_ == RequestMethod::POST) { + if (!postData_.empty()) { + // Note: Naett does not take ownership over the body. + options.push_back(naettBody(postData_.data(), (int)postData_.size())); + } + } else { + _dbg_assert_(postData_.empty()); + } + // 30 s timeout - not sure what's reasonable? + options.push_back(naettTimeout(30 * 1000)); // milliseconds + + + const naettOption **opts = (const naettOption **)options.data(); + req_ = naettRequestWithOptions(url_.c_str(), (int)options.size(), opts); res_ = naettMake(req_); } @@ -46,51 +65,55 @@ bool HTTPSDownload::Done() { if (completed_) return true; - if (naettComplete(res_)) { - resultCode_ = naettGetStatus(res_); - if (resultCode_ < 0) { - // It's a naett error. Translate and handle. - switch (resultCode_) { - case naettConnectionError: // -1 - ERROR_LOG(IO, "Connection error"); - break; - case naettProtocolError: // -2 - ERROR_LOG(IO, "Protocol error"); - break; - case naettReadError: // -3 - ERROR_LOG(IO, "Read error"); - break; - case naettWriteError: // -4 - ERROR_LOG(IO, "Write error"); - break; - case naettGenericError: // -4 - ERROR_LOG(IO, "Generic error"); - break; - } - } else if (resultCode_ == 200) { - int bodyLength; - const void *body = naettGetBody(res_, &bodyLength); - char *dest = buffer_.Append(bodyLength); - memcpy(dest, body, bodyLength); - - if (!outfile_.empty() && !buffer_.FlushToFile(outfile_)) { - ERROR_LOG(IO, "Failed writing download to '%s'", outfile_.c_str()); - } - } else { - WARN_LOG(IO, "Naett request failed: %d", resultCode_); - failed_ = true; - } - - completed_ = true; - - if (callback_) - callback_(*this); - return true; + if (!naettComplete(res_)) { + // Not done yet, return and try again later. + return false; } - return false; + resultCode_ = naettGetStatus(res_); + if (resultCode_ < 0) { + // It's a naett error. Translate and handle. + switch (resultCode_) { + case naettConnectionError: // -1 + ERROR_LOG(IO, "Connection error"); + break; + case naettProtocolError: // -2 + ERROR_LOG(IO, "Protocol error"); + break; + case naettReadError: // -3 + ERROR_LOG(IO, "Read error"); + break; + case naettWriteError: // -4 + ERROR_LOG(IO, "Write error"); + break; + case naettGenericError: // -4 + ERROR_LOG(IO, "Generic error"); + break; + default: + ERROR_LOG(IO, "Unhandled naett error %d", resultCode_); + break; + } + failed_ = true; + } else if (resultCode_ == 200) { + int bodyLength; + const void *body = naettGetBody(res_, &bodyLength); + char *dest = buffer_.Append(bodyLength); + memcpy(dest, body, bodyLength); + if (!outfile_.empty() && !buffer_.FlushToFile(outfile_)) { + ERROR_LOG(IO, "Failed writing download to '%s'", outfile_.c_str()); + } + } else { + WARN_LOG(IO, "Naett request failed: %d", resultCode_); + failed_ = true; + } + + completed_ = true; + + if (callback_) + callback_(*this); + return true; } -} // namespace +} // namespace http #endif // HTTPS_NOT_AVAILABLE From ae354a7639f8560fc32b78ee1cd5da41007b7712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 21 Jul 2023 10:19:16 +0200 Subject: [PATCH 026/167] Store: Use HTTPS based on availability --- UI/Store.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/UI/Store.cpp b/UI/Store.cpp index b8fe72bc2a..dbe14d46dd 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -35,7 +35,12 @@ #include "UI/EmuScreen.h" #include "UI/Store.h" -const std::string storeBaseUrl = "https://store.ppsspp.org/"; +const char *storeBaseUrlHttp = "http://store.ppsspp.org/"; +const char *storeBaseUrlHttps = "https://store.ppsspp.org/"; + +static std::string StoreBaseUrl() { + return System_GetPropertyBool(SYSPROP_SUPPORTS_HTTPS) ? storeBaseUrlHttps : storeBaseUrlHttp; +} // baseUrl is assumed to have a trailing slash, and not contain any subdirectories. std::string ResolveUrl(std::string baseUrl, std::string url) { @@ -279,7 +284,7 @@ void ProductView::CreateViews() { Clear(); if (!entry_.iconURL.empty()) { - Add(new HttpImageFileView(&g_DownloadManager, ResolveUrl(storeBaseUrl, entry_.iconURL), IS_FIXED))->SetFixedSize(144, 88); + Add(new HttpImageFileView(&g_DownloadManager, ResolveUrl(StoreBaseUrl(), entry_.iconURL), IS_FIXED))->SetFixedSize(144, 88); } Add(new TextView(entry_.name)); Add(new TextView(entry_.author)); @@ -350,7 +355,7 @@ void ProductView::Update() { std::string ProductView::DownloadURL() { if (entry_.downloadURL.empty()) { // Construct the URL. - return storeBaseUrl + "files/" + entry_.file + ".zip"; + return StoreBaseUrl() + "files/" + entry_.file + ".zip"; } else { // Use the provided URL, for external hosting. return entry_.downloadURL; @@ -405,7 +410,7 @@ StoreScreen::StoreScreen() { lang_ = g_Config.sLanguageIni; loading_ = true; - std::string indexPath = storeBaseUrl + "index.json"; + std::string indexPath = StoreBaseUrl() + "index.json"; const char *acceptMime = "application/json, */*; q=0.8"; listing_ = g_DownloadManager.StartDownload(indexPath, Path(), http::ProgressBarMode::DELAYED, acceptMime); } From 9decdd198e4e08db9169143c82f6c7cdb6f01eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 21 Jul 2023 10:27:31 +0200 Subject: [PATCH 027/167] Address more feedback, minor cleanup --- Common/Net/HTTPNaettRequest.cpp | 5 +++-- Core/Util/PortManager.cpp | 2 -- Core/Util/PortManager.h | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Common/Net/HTTPNaettRequest.cpp b/Common/Net/HTTPNaettRequest.cpp index c56295aae4..bb6888cc4a 100644 --- a/Common/Net/HTTPNaettRequest.cpp +++ b/Common/Net/HTTPNaettRequest.cpp @@ -70,7 +70,8 @@ bool HTTPSDownload::Done() { return false; } - resultCode_ = naettGetStatus(res_); + // -1000 is a code specified by us to represent cancellation, that is unlikely to ever collide with naett error codes. + resultCode_ = IsCancelled() ? -1000 : naettGetStatus(res_); if (resultCode_ < 0) { // It's a naett error. Translate and handle. switch (resultCode_) { @@ -86,7 +87,7 @@ bool HTTPSDownload::Done() { case naettWriteError: // -4 ERROR_LOG(IO, "Write error"); break; - case naettGenericError: // -4 + case naettGenericError: // -5 ERROR_LOG(IO, "Generic error"); break; default: diff --git a/Core/Util/PortManager.cpp b/Core/Util/PortManager.cpp index d4103e0e75..cfb81e9dbd 100644 --- a/Core/Util/PortManager.cpp +++ b/Core/Util/PortManager.cpp @@ -49,8 +49,6 @@ std::recursive_mutex upnpLock; std::deque upnpReqs; PortManager::PortManager(): - urls(0), - datas(0), m_InitState(UPNP_INITSTATE_NONE), m_LocalPort(UPNP_LOCAL_PORT_ANY), m_leaseDuration("43200") { diff --git a/Core/Util/PortManager.h b/Core/Util/PortManager.h index f284788a38..31b9f67f95 100644 --- a/Core/Util/PortManager.h +++ b/Core/Util/PortManager.h @@ -108,8 +108,8 @@ private: // Uninitialize/Reset the state void Terminate(); - struct UPNPUrls* urls = NULL; - struct IGDdatas* datas = NULL; + struct UPNPUrls* urls = nullptr; + struct IGDdatas* datas = nullptr; int m_InitState = UPNP_INITSTATE_NONE; int m_LocalPort = UPNP_LOCAL_PORT_ANY; From 61db21b12d39699674aaa71ea8a33f45058d2360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 21 Jul 2023 10:49:01 +0200 Subject: [PATCH 028/167] Cleanup, fix so we can run RetroAchievements with https, and start doing that --- Common/Net/HTTPClient.h | 10 ---------- Common/Net/HTTPNaettRequest.cpp | 3 +-- Common/Net/HTTPNaettRequest.h | 10 ---------- Common/Net/HTTPRequest.h | 12 ++++++++++-- Core/RetroAchievements.cpp | 6 ++++-- 5 files changed, 15 insertions(+), 26 deletions(-) diff --git a/Common/Net/HTTPClient.h b/Common/Net/HTTPClient.h index 85543bb53c..d3e91d027a 100644 --- a/Common/Net/HTTPClient.h +++ b/Common/Net/HTTPClient.h @@ -96,14 +96,6 @@ public: HTTPDownload(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode = ProgressBarMode::DELAYED, const std::string &name = ""); ~HTTPDownload(); - void SetAccept(const char *mime) override { - acceptMime_ = mime; - } - - void SetUserAgent(const std::string &userAgent) override { - userAgent_ = userAgent; - } - void Start() override; void Join() override; @@ -135,12 +127,10 @@ private: RequestMethod method_; std::string postData_; - std::string userAgent_; Buffer buffer_; std::vector responseHeaders_; Path outfile_; std::thread thread_; - const char *acceptMime_ = "*/*"; std::string postMime_; int resultCode_ = 0; bool completed_ = false; diff --git a/Common/Net/HTTPNaettRequest.cpp b/Common/Net/HTTPNaettRequest.cpp index bb6888cc4a..f706de794d 100644 --- a/Common/Net/HTTPNaettRequest.cpp +++ b/Common/Net/HTTPNaettRequest.cpp @@ -110,8 +110,7 @@ bool HTTPSDownload::Done() { completed_ = true; - if (callback_) - callback_(*this); + // The callback will be called later. return true; } diff --git a/Common/Net/HTTPNaettRequest.h b/Common/Net/HTTPNaettRequest.h index 6a9f62eba9..064c9c70ed 100644 --- a/Common/Net/HTTPNaettRequest.h +++ b/Common/Net/HTTPNaettRequest.h @@ -16,14 +16,6 @@ public: HTTPSDownload(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode = ProgressBarMode::DELAYED, const std::string &name = ""); ~HTTPSDownload(); - void SetAccept(const char *mime) override { - acceptMime_ = mime; - } - - void SetUserAgent(const std::string &userAgent) override { - userAgent_ = userAgent; - } - void Start() override; void Join() override; @@ -51,11 +43,9 @@ public: private: RequestMethod method_; std::string postData_; - std::string userAgent_; Buffer buffer_; std::vector responseHeaders_; Path outfile_; - const char *acceptMime_ = "*/*"; std::string postMime_; int resultCode_ = 0; bool completed_ = false; diff --git a/Common/Net/HTTPRequest.h b/Common/Net/HTTPRequest.h index 7c55c5bd85..fc83c3f09b 100644 --- a/Common/Net/HTTPRequest.h +++ b/Common/Net/HTTPRequest.h @@ -26,8 +26,13 @@ public: Download(const std::string &url, const std::string &name, bool *cancelled) : url_(url), name_(name), progress_(cancelled) {} virtual ~Download() {} - virtual void SetAccept(const char *mime) = 0; - virtual void SetUserAgent(const std::string &userAgent) = 0; + void SetAccept(const char *mime) { + acceptMime_ = mime; + } + + void SetUserAgent(const std::string &userAgent) { + userAgent_ = userAgent; + } // NOTE: Completion callbacks (which these are) are deferred until RunCallback is called. This is so that // the call will end up on the thread that calls g_DownloadManager.Update(). @@ -65,6 +70,9 @@ protected: std::function callback_; std::string url_; std::string name_; + const char *acceptMime_ = "*/*"; + std::string userAgent_; + net::RequestProgress progress_; private: diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index 84302c8123..3821470ada 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -349,8 +349,10 @@ void Initialize() { // Provide a logging function to simplify debugging rc_client_enable_logging(g_rcClient, RC_CLIENT_LOG_LEVEL_VERBOSE, log_message_callback); - // Disable SSL for now. - rc_client_set_host(g_rcClient, "http://retroachievements.org"); + if (!System_GetPropertyBool(SYSPROP_SUPPORTS_HTTPS)) { + // Disable SSL if not supported by our platform implementation. + rc_client_set_host(g_rcClient, "http://retroachievements.org"); + } rc_client_set_event_handler(g_rcClient, event_handler_callback); From 9fdd9645de72f781b71b1d54282dec4549f3c852 Mon Sep 17 00:00:00 2001 From: Lubos Date: Fri, 21 Jul 2023 17:22:36 +0200 Subject: [PATCH 029/167] OpenXR - Enable 6DoF in cinema mode --- Common/VR/PPSSPPVR.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp index 85bfe7ff58..187fe5759a 100644 --- a/Common/VR/PPSSPPVR.cpp +++ b/Common/VR/PPSSPPVR.cpp @@ -907,7 +907,7 @@ void UpdateVRViewMatrices() { XrQuaternionf_ToMatrix4f(&invView.orientation, M); // Apply 6Dof head movement - if (!flatScreen && g_Config.bEnable6DoF && !g_Config.bHeadRotationEnabled && (g_Config.iCameraPitch == 0)) { + if (g_Config.bEnable6DoF && !g_Config.bHeadRotationEnabled && (g_Config.iCameraPitch == 0)) { M[3] -= vrView[0].pose.position.x * (vrMirroring[VR_MIRRORING_AXIS_X] ? -1.0f : 1.0f) * scale; M[7] -= vrView[0].pose.position.y * (vrMirroring[VR_MIRRORING_AXIS_Y] ? -1.0f : 1.0f) * scale; M[11] -= vrView[0].pose.position.z * (vrMirroring[VR_MIRRORING_AXIS_Z] ? -1.0f : 1.0f) * scale; From c17a559188904f6d3f56e46cc80d7a2d29054f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 21 Jul 2023 17:49:08 +0200 Subject: [PATCH 030/167] Update to a patched naett with PRs #18 and #17 --- .gitmodules | 2 +- ext/naett | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 2dd1e55e80..af29087557 100644 --- a/.gitmodules +++ b/.gitmodules @@ -49,4 +49,4 @@ url = https://github.com/RetroAchievements/rcheevos.git [submodule "ext/naett"] path = ext/naett - url = https://github.com/erkkah/naett.git + url = https://github.com/hrydgard/naett.git diff --git a/ext/naett b/ext/naett index 6206522f33..24438d3d6c 160000 --- a/ext/naett +++ b/ext/naett @@ -1 +1 @@ -Subproject commit 6206522f33e2bc49fcd0c2063ff8b4e9734416c9 +Subproject commit 24438d3d6c6e4639b51237ac34cebf25781d5f0a From 9535b1674044e6ae90763a7c12a4e312247d1a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 21 Jul 2023 17:50:49 +0200 Subject: [PATCH 031/167] Use the patched naett functions to implement progress updates --- Common/Net/HTTPClient.cpp | 23 +---------------------- Common/Net/HTTPClient.h | 2 -- Common/Net/HTTPNaettRequest.cpp | 13 ++++++++++--- Common/Net/HTTPNaettRequest.h | 1 - Common/Net/HTTPRequest.cpp | 25 +++++++++++++++++++++++++ Common/Net/HTTPRequest.h | 4 +++- Common/Net/NetBuffer.cpp | 12 ++++++++++++ Common/Net/NetBuffer.h | 11 +---------- 8 files changed, 52 insertions(+), 39 deletions(-) diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index 5638e30529..9fc37de082 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -445,28 +445,7 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector responseHeaders_; @@ -136,7 +135,6 @@ private: bool completed_ = false; bool failed_ = false; bool cancelled_ = false; - ProgressBarMode progressBarMode_; bool joined_ = false; }; diff --git a/Common/Net/HTTPNaettRequest.cpp b/Common/Net/HTTPNaettRequest.cpp index f706de794d..55963e9558 100644 --- a/Common/Net/HTTPNaettRequest.cpp +++ b/Common/Net/HTTPNaettRequest.cpp @@ -5,6 +5,7 @@ #include "Common/Net/HTTPRequest.h" #include "Common/Net/HTTPNaettRequest.h" #include "Common/Thread/ThreadUtil.h" +#include "Common/StringUtils.h" #include "Common/Log.h" #include "ext/naett/naett.h" @@ -12,7 +13,7 @@ namespace http { HTTPSDownload::HTTPSDownload(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode, const std::string &name) - : Download(url, name, &cancelled_), method_(method), postData_(postData), postMime_(postMime), outfile_(outfile), progressBarMode_(progressBarMode) { + : Download(method, url, name, &cancelled_, progressBarMode), method_(method), postData_(postData), postMime_(postMime), outfile_(outfile) { } HTTPSDownload::~HTTPSDownload() { @@ -40,10 +41,11 @@ void HTTPSDownload::Start() { // 30 s timeout - not sure what's reasonable? options.push_back(naettTimeout(30 * 1000)); // milliseconds - const naettOption **opts = (const naettOption **)options.data(); req_ = naettRequestWithOptions(url_.c_str(), (int)options.size(), opts); res_ = naettMake(req_); + + progress_.Update(0, 0, false); } void HTTPSDownload::Join() { @@ -66,7 +68,9 @@ bool HTTPSDownload::Done() { return true; if (!naettComplete(res_)) { - // Not done yet, return and try again later. + int total = 0; + int size = naettGetTotalBytesRead(res_, &total); + progress_.Update(size, total, false); return false; } @@ -95,6 +99,7 @@ bool HTTPSDownload::Done() { break; } failed_ = true; + progress_.Update(0, 0, true); } else if (resultCode_ == 200) { int bodyLength; const void *body = naettGetBody(res_, &bodyLength); @@ -103,9 +108,11 @@ bool HTTPSDownload::Done() { if (!outfile_.empty() && !buffer_.FlushToFile(outfile_)) { ERROR_LOG(IO, "Failed writing download to '%s'", outfile_.c_str()); } + progress_.Update(bodyLength, bodyLength, true); } else { WARN_LOG(IO, "Naett request failed: %d", resultCode_); failed_ = true; + progress_.Update(0, 0, true); } completed_ = true; diff --git a/Common/Net/HTTPNaettRequest.h b/Common/Net/HTTPNaettRequest.h index 064c9c70ed..4fa5c93d16 100644 --- a/Common/Net/HTTPNaettRequest.h +++ b/Common/Net/HTTPNaettRequest.h @@ -51,7 +51,6 @@ private: bool completed_ = false; bool failed_ = false; bool cancelled_ = false; - ProgressBarMode progressBarMode_; bool joined_ = false; // Naett state diff --git a/Common/Net/HTTPRequest.cpp b/Common/Net/HTTPRequest.cpp index 318470a09d..e982ff81f9 100644 --- a/Common/Net/HTTPRequest.cpp +++ b/Common/Net/HTTPRequest.cpp @@ -3,9 +3,34 @@ #include "Common/Net/HTTPNaettRequest.h" #include "Common/TimeUtil.h" #include "Common/StringUtils.h" +#include "Common/Log.h" +#include "Common/System/OSD.h" namespace http { +Download::Download(RequestMethod method, const std::string &url, const std::string &name, bool *cancelled, ProgressBarMode mode) : method_(method), url_(url), name_(name), progress_(cancelled), progressBarMode_(mode) { + progress_.callback = [=](int64_t bytes, int64_t contentLength, bool done) { + std::string message; + if (!name_.empty()) { + message = name_; + } else { + std::size_t pos = url_.rfind('/'); + if (pos != std::string::npos) { + message = url_.substr(pos + 1); + } else { + message = url_; + } + } + if (progressBarMode_ != ProgressBarMode::NONE) { + if (!done) { + g_OSD.SetProgressBar(url_, std::move(message), 0.0f, (float)contentLength, (float)bytes, progressBarMode_ == ProgressBarMode::DELAYED ? 3.0f : 0.0f); // delay 3 seconds before showing. + } else { + g_OSD.RemoveProgressBar(url_, Failed() ? false : true, 0.5f); + } + } + }; +} + bool RequestManager::IsHttpsUrl(const std::string &url) { return startsWith(url, "https:"); } diff --git a/Common/Net/HTTPRequest.h b/Common/Net/HTTPRequest.h index fc83c3f09b..0e7686009b 100644 --- a/Common/Net/HTTPRequest.h +++ b/Common/Net/HTTPRequest.h @@ -23,7 +23,7 @@ enum class ProgressBarMode { // Abstract request. class Download { public: - Download(const std::string &url, const std::string &name, bool *cancelled) : url_(url), name_(name), progress_(cancelled) {} + Download(RequestMethod method, const std::string &url, const std::string &name, bool *cancelled, ProgressBarMode mode); virtual ~Download() {} void SetAccept(const char *mime) { @@ -68,12 +68,14 @@ public: protected: std::function callback_; + RequestMethod method_; std::string url_; std::string name_; const char *acceptMime_ = "*/*"; std::string userAgent_; net::RequestProgress progress_; + ProgressBarMode progressBarMode_; private: }; diff --git a/Common/Net/NetBuffer.cpp b/Common/Net/NetBuffer.cpp index 3cdbbcd189..06310a6c83 100644 --- a/Common/Net/NetBuffer.cpp +++ b/Common/Net/NetBuffer.cpp @@ -22,6 +22,18 @@ namespace net { +void RequestProgress::Update(int64_t downloaded, int64_t totalBytes, bool done) { + if (totalBytes) { + progress = (double)downloaded / (double)totalBytes; + } else { + progress = 0.01f; + } + + if (callback) { + callback(downloaded, totalBytes, done); + } +} + bool Buffer::FlushSocket(uintptr_t sock, double timeout, bool *cancelled) { static constexpr float CANCEL_INTERVAL = 0.25f; for (size_t pos = 0, end = data_.size(); pos < end; ) { diff --git a/Common/Net/NetBuffer.h b/Common/Net/NetBuffer.h index 85b1421d28..32525ad3f7 100644 --- a/Common/Net/NetBuffer.h +++ b/Common/Net/NetBuffer.h @@ -12,16 +12,7 @@ public: RequestProgress() {} explicit RequestProgress(bool *c) : cancelled(c) {} - void Update(int64_t downloaded, int64_t totalBytes, bool done) { - if (totalBytes) { - progress = (double)downloaded / (double)totalBytes; - } else { - progress = 0.01f; - } - if (callback) { - callback(downloaded, totalBytes, done); - } - } + void Update(int64_t downloaded, int64_t totalBytes, bool done); float progress = 0.0f; float kBps = 0.0f; From ac6301db476f4e55210b045e30dc17b729e2831a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 21 Jul 2023 17:52:38 +0200 Subject: [PATCH 032/167] Use the new function in patched naett to update user agent --- Common/Net/HTTPNaettRequest.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Common/Net/HTTPNaettRequest.cpp b/Common/Net/HTTPNaettRequest.cpp index 55963e9558..87ca991ee9 100644 --- a/Common/Net/HTTPNaettRequest.cpp +++ b/Common/Net/HTTPNaettRequest.cpp @@ -27,6 +27,7 @@ void HTTPSDownload::Start() { std::vector options; options.push_back(naettMethod(method_ == RequestMethod::GET ? "GET" : "POST")); options.push_back(naettHeader("Accept", acceptMime_)); + options.push_back(naettUserAgent(userAgent_.c_str())); if (!postMime_.empty()) { options.push_back(naettHeader("Content-Type", postMime_.c_str())); } From 93bb1130094106a0aa88771e8b4d148ea53e382b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 21 Jul 2023 22:04:05 +0200 Subject: [PATCH 033/167] Common: Rename Download to Request, and the old Request to ServerRequest. --- Common/Net/HTTPClient.cpp | 20 +++++++++--------- Common/Net/HTTPClient.h | 6 +++--- Common/Net/HTTPNaettRequest.cpp | 12 +++++------ Common/Net/HTTPNaettRequest.h | 6 +++--- Common/Net/HTTPRequest.cpp | 36 ++++++++++++++++----------------- Common/Net/HTTPRequest.h | 26 +++++++++++------------- Common/Net/HTTPServer.cpp | 22 ++++++++++---------- Common/Net/HTTPServer.h | 16 +++++++-------- Common/Net/WebsocketServer.cpp | 2 +- Common/Net/WebsocketServer.h | 2 +- Core/Config.cpp | 2 +- Core/Config.h | 4 ++-- Core/Debugger/WebSocket.cpp | 2 +- Core/Debugger/WebSocket.h | 4 ++-- Core/RetroAchievements.cpp | 6 +++--- Core/Util/GameManager.h | 2 +- Core/WebServer.cpp | 12 +++++------ UI/DevScreens.h | 4 ++-- UI/Store.cpp | 8 ++++---- UI/Store.h | 4 ++-- 20 files changed, 97 insertions(+), 99 deletions(-) diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index 9fc37de082..7c810ec975 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -444,21 +444,21 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector RequestManager::StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime) { - std::shared_ptr dl; +std::shared_ptr RequestManager::StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime) { + std::shared_ptr dl; if (IsHttpsUrl(url)) { #ifndef HTTPS_NOT_AVAILABLE - dl.reset(new HTTPSDownload(RequestMethod::GET, url, "", "", outfile, mode)); + dl.reset(new HTTPSRequest(RequestMethod::GET, url, "", "", outfile, mode)); #else - return std::shared_ptr(); + return std::shared_ptr(); #endif } else { - dl.reset(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode)); + dl.reset(new HTTPRequest(RequestMethod::GET, url, "", "", outfile, mode)); } if (!userAgent_.empty()) @@ -56,22 +56,22 @@ std::shared_ptr RequestManager::StartDownload(const std::string &url, return dl; } -std::shared_ptr RequestManager::StartDownloadWithCallback( +std::shared_ptr RequestManager::StartDownloadWithCallback( const std::string &url, const Path &outfile, ProgressBarMode mode, - std::function callback, + std::function callback, const std::string &name, const char *acceptMime) { - std::shared_ptr dl; + std::shared_ptr dl; if (IsHttpsUrl(url)) { #ifndef HTTPS_NOT_AVAILABLE - dl.reset(new HTTPSDownload(RequestMethod::GET, url, "", "", outfile, mode, name)); + dl.reset(new HTTPSRequest(RequestMethod::GET, url, "", "", outfile, mode, name)); #else - return std::shared_ptr(); + return std::shared_ptr(); #endif } else { - dl.reset(new HTTPDownload(RequestMethod::GET, url, "", "", outfile, mode, name)); + dl.reset(new HTTPRequest(RequestMethod::GET, url, "", "", outfile, mode, name)); } if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); @@ -83,22 +83,22 @@ std::shared_ptr RequestManager::StartDownloadWithCallback( return dl; } -std::shared_ptr RequestManager::AsyncPostWithCallback( +std::shared_ptr RequestManager::AsyncPostWithCallback( const std::string &url, const std::string &postData, const std::string &postMime, ProgressBarMode mode, - std::function callback, + std::function callback, const std::string &name) { - std::shared_ptr dl; + std::shared_ptr dl; if (IsHttpsUrl(url)) { #ifndef HTTPS_NOT_AVAILABLE - dl.reset(new HTTPSDownload(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); + dl.reset(new HTTPSRequest(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); #else - return std::shared_ptr(); + return std::shared_ptr(); #endif } else { - dl.reset(new HTTPDownload(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); + dl.reset(new HTTPRequest(RequestMethod::POST, url, postData, postMime, Path(), mode, name)); } if (!userAgent_.empty()) dl->SetUserAgent(userAgent_); diff --git a/Common/Net/HTTPRequest.h b/Common/Net/HTTPRequest.h index 0e7686009b..b04b04d87c 100644 --- a/Common/Net/HTTPRequest.h +++ b/Common/Net/HTTPRequest.h @@ -21,10 +21,10 @@ enum class ProgressBarMode { }; // Abstract request. -class Download { +class Request { public: - Download(RequestMethod method, const std::string &url, const std::string &name, bool *cancelled, ProgressBarMode mode); - virtual ~Download() {} + Request(RequestMethod method, const std::string &url, const std::string &name, bool *cancelled, ProgressBarMode mode); + virtual ~Request() {} void SetAccept(const char *mime) { acceptMime_ = mime; @@ -36,7 +36,7 @@ public: // NOTE: Completion callbacks (which these are) are deferred until RunCallback is called. This is so that // the call will end up on the thread that calls g_DownloadManager.Update(). - void SetCallback(std::function callback) { + void SetCallback(std::function callback) { callback_ = callback; } void RunCallback() { @@ -67,7 +67,7 @@ public: virtual const Buffer &buffer() const = 0; protected: - std::function callback_; + std::function callback_; RequestMethod method_; std::string url_; std::string name_; @@ -76,8 +76,6 @@ protected: net::RequestProgress progress_; ProgressBarMode progressBarMode_; - -private: }; using std::shared_ptr; @@ -88,22 +86,22 @@ public: CancelAll(); } - std::shared_ptr StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime = nullptr); + std::shared_ptr StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime = nullptr); - std::shared_ptr StartDownloadWithCallback( + std::shared_ptr StartDownloadWithCallback( const std::string &url, const Path &outfile, ProgressBarMode mode, - std::function callback, + std::function callback, const std::string &name = "", const char *acceptMime = nullptr); - std::shared_ptr AsyncPostWithCallback( + std::shared_ptr AsyncPostWithCallback( const std::string &url, const std::string &postData, const std::string &postMime, // Use postMime = "application/x-www-form-urlencoded" for standard form-style posts, such as used by retroachievements. For encoding form data manually we have MultipartFormDataEncoder. ProgressBarMode mode, - std::function callback, + std::function callback, const std::string &name = ""); // Drops finished downloads from the list. @@ -118,10 +116,10 @@ public: private: bool IsHttpsUrl(const std::string &url); - std::vector> downloads_; + std::vector> downloads_; // These get copied to downloads_ in Update(). It's so that callbacks can add new downloads // while running. - std::vector> newDownloads_; + std::vector> newDownloads_; std::string userAgent_; }; diff --git a/Common/Net/HTTPServer.cpp b/Common/Net/HTTPServer.cpp index e1fa53bc6e..3822083b79 100644 --- a/Common/Net/HTTPServer.cpp +++ b/Common/Net/HTTPServer.cpp @@ -60,7 +60,7 @@ namespace http { // Note: charset here helps prevent XSS. const char *const DEFAULT_MIME_TYPE = "text/html; charset=utf-8"; -Request::Request(int fd) +ServerRequest::ServerRequest(int fd) : fd_(fd) { in_ = new net::InputSink(fd); out_ = new net::OutputSink(fd); @@ -73,7 +73,7 @@ Request::Request(int fd) } } -Request::~Request() { +ServerRequest::~ServerRequest() { Close(); if (!in_->Empty()) { @@ -86,7 +86,7 @@ Request::~Request() { delete out_; } -void Request::WriteHttpResponseHeader(const char *ver, int status, int64_t size, const char *mimeType, const char *otherHeaders) const { +void ServerRequest::WriteHttpResponseHeader(const char *ver, int status, int64_t size, const char *mimeType, const char *otherHeaders) const { const char *statusStr; switch (status) { case 200: statusStr = "OK"; break; @@ -123,18 +123,18 @@ void Request::WriteHttpResponseHeader(const char *ver, int status, int64_t size, buffer->Push("\r\n"); } -void Request::WritePartial() const { +void ServerRequest::WritePartial() const { _assert_(fd_); out_->Flush(); } -void Request::Write() { +void ServerRequest::Write() { _assert_(fd_); WritePartial(); Close(); } -void Request::Close() { +void ServerRequest::Close() { if (fd_) { closesocket(fd_); fd_ = 0; @@ -317,7 +317,7 @@ void Server::Stop() { } void Server::HandleConnection(int conn_fd) { - Request request(conn_fd); + ServerRequest request(conn_fd); if (!request.IsOK()) { WARN_LOG(IO, "Bad request, ignoring."); return; @@ -331,11 +331,11 @@ void Server::HandleConnection(int conn_fd) { request.Write(); } -void Server::HandleRequest(const Request &request) { +void Server::HandleRequest(const ServerRequest &request) { HandleRequestDefault(request); } -void Server::HandleRequestDefault(const Request &request) { +void Server::HandleRequestDefault(const ServerRequest &request) { if (request.resource() == nullptr) { fallback_(request); return; @@ -350,14 +350,14 @@ void Server::HandleRequestDefault(const Request &request) { } } -void Server::Handle404(const Request &request) { +void Server::Handle404(const ServerRequest &request) { INFO_LOG(IO, "No handler for '%s', falling back to 404.", request.resource()); const char *payload = "404 not found\r\n"; request.WriteHttpResponseHeader("1.0", 404, (int)strlen(payload)); request.Out()->Push(payload); } -void Server::HandleListing(const Request &request) { +void Server::HandleListing(const ServerRequest &request) { request.WriteHttpResponseHeader("1.0", 200, -1, "text/plain"); for (auto iter = handlers_.begin(); iter != handlers_.end(); ++iter) { request.Out()->Printf("%s\n", iter->first.c_str()); diff --git a/Common/Net/HTTPServer.h b/Common/Net/HTTPServer.h index 40e336bb5a..c9051226a6 100644 --- a/Common/Net/HTTPServer.h +++ b/Common/Net/HTTPServer.h @@ -25,10 +25,10 @@ class OutputSink; namespace http { -class Request { +class ServerRequest { public: - Request(int fd); - ~Request(); + ServerRequest(int fd); + ~ServerRequest(); const char *resource() const { return header_.resource; @@ -75,7 +75,7 @@ public: Server(NewThreadExecutor *executor); virtual ~Server(); - typedef std::function UrlHandlerFunc; + typedef std::function UrlHandlerFunc; typedef std::map UrlHandlerMap; // Runs forever, serving request. If you want to do something else than serve pages, @@ -94,7 +94,7 @@ public: // If you want to customize things at a lower level than just a simple path handler, // then inherit and override this. Implementations should forward to HandleRequestDefault // if they don't recognize the url. - virtual void HandleRequest(const Request &request); + virtual void HandleRequest(const ServerRequest &request); int Port() { return port_; @@ -107,11 +107,11 @@ private: void HandleConnection(int conn_fd); // Things like default 404, etc. - void HandleRequestDefault(const Request &request); + void HandleRequestDefault(const ServerRequest &request); // Neat built-in handlers that are tied to the server. - void HandleListing(const Request &request); - void Handle404(const Request &request); + void HandleListing(const ServerRequest &request); + void Handle404(const ServerRequest &request); int listener_; int port_ = 0; diff --git a/Common/Net/WebsocketServer.cpp b/Common/Net/WebsocketServer.cpp index 3b6f98ed27..e55c0644f7 100644 --- a/Common/Net/WebsocketServer.cpp +++ b/Common/Net/WebsocketServer.cpp @@ -69,7 +69,7 @@ static bool ListContainsNoCase(const std::string &list, const std::string value) return false; } -WebSocketServer *WebSocketServer::CreateAsUpgrade(const http::Request &request, const std::string &protocol) { +WebSocketServer *WebSocketServer::CreateAsUpgrade(const http::ServerRequest &request, const std::string &protocol) { auto requireHeader = [&](const char *name, const char *expected) { std::string val; if (!request.GetHeader(name, &val)) { diff --git a/Common/Net/WebsocketServer.h b/Common/Net/WebsocketServer.h index 7a05b0f03a..591e800e94 100644 --- a/Common/Net/WebsocketServer.h +++ b/Common/Net/WebsocketServer.h @@ -29,7 +29,7 @@ enum class WebSocketClose : uint16_t { // RFC 6455 class WebSocketServer { public: - static WebSocketServer *CreateAsUpgrade(const http::Request &request, const std::string &protocol = ""); + static WebSocketServer *CreateAsUpgrade(const http::ServerRequest &request, const std::string &protocol = ""); void Send(const std::string &str); void Send(const std::vector &payload); diff --git a/Core/Config.cpp b/Core/Config.cpp index 70eb254f2e..820cf0e8c6 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1363,7 +1363,7 @@ void Config::NotifyUpdatedCpuCore() { #define PPSSPP_GIT_VERSION "v0.0.1-gaaaaaaaaa" #endif -void Config::DownloadCompletedCallback(http::Download &download) { +void Config::DownloadCompletedCallback(http::Request &download) { if (download.ResultCode() != 200) { ERROR_LOG(LOADER, "Failed to download %s: %d", download.url().c_str(), download.ResultCode()); return; diff --git a/Core/Config.h b/Core/Config.h index 98edbd35b5..d62d846a0f 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -41,7 +41,7 @@ enum ChatPositions { }; namespace http { - class Download; + class Request; class RequestManager; } @@ -543,7 +543,7 @@ public: void RemoveRecent(const std::string &file); void CleanRecent(); - static void DownloadCompletedCallback(http::Download &download); + static void DownloadCompletedCallback(http::Request &download); void DismissUpgrade(); void ResetControlLayout(); diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index e704a5c00e..1ba39ad0ec 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -131,7 +131,7 @@ static void SetupDebuggerLock() { } } -void HandleDebuggerRequest(const http::Request &request) { +void HandleDebuggerRequest(const http::ServerRequest &request) { net::WebSocketServer *ws = net::WebSocketServer::CreateAsUpgrade(request, "debugger.ppsspp.org"); if (!ws) return; diff --git a/Core/Debugger/WebSocket.h b/Core/Debugger/WebSocket.h index 0151ca686a..f05856db20 100644 --- a/Core/Debugger/WebSocket.h +++ b/Core/Debugger/WebSocket.h @@ -18,9 +18,9 @@ #pragma once namespace http { -class Request; +class ServerRequest; } -void HandleDebuggerRequest(const http::Request &request); +void HandleDebuggerRequest(const http::ServerRequest &request); // Note: blocks. void StopAllDebuggers(); diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index 3821470ada..9bac7b9c5e 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -177,7 +177,7 @@ static void server_call_callback(const rc_api_request_t *request, // If post data is provided, we need to make a POST request, otherwise, a GET request will suffice. auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS); if (request->post_data) { - std::shared_ptr download = g_DownloadManager.AsyncPostWithCallback(std::string(request->url), std::string(request->post_data), "application/x-www-form-urlencoded", http::ProgressBarMode::DELAYED, [=](http::Download &download) { + std::shared_ptr download = g_DownloadManager.AsyncPostWithCallback(std::string(request->url), std::string(request->post_data), "application/x-www-form-urlencoded", http::ProgressBarMode::DELAYED, [=](http::Request &download) { std::string buffer; download.buffer().TakeAll(&buffer); rc_api_server_response_t response{}; @@ -187,7 +187,7 @@ static void server_call_callback(const rc_api_request_t *request, callback(&response, callback_data); }, ac->T("Contacting RetroAchievements server...")); } else { - std::shared_ptr download = g_DownloadManager.StartDownloadWithCallback(std::string(request->url), Path(), http::ProgressBarMode::DELAYED, [=](http::Download &download) { + std::shared_ptr download = g_DownloadManager.StartDownloadWithCallback(std::string(request->url), Path(), http::ProgressBarMode::DELAYED, [=](http::Request &download) { std::string buffer; download.buffer().TakeAll(&buffer); rc_api_server_response_t response{}; @@ -543,7 +543,7 @@ bool HasAchievementsOrLeaderboards() { void DownloadImageIfMissing(const std::string &cache_key, std::string &&url) { if (g_iconCache.MarkPending(cache_key)) { INFO_LOG(ACHIEVEMENTS, "Downloading image: %s (%s)", url.c_str(), cache_key.c_str()); - g_DownloadManager.StartDownloadWithCallback(url, Path(), http::ProgressBarMode::NONE, [cache_key](http::Download &download) { + g_DownloadManager.StartDownloadWithCallback(url, Path(), http::ProgressBarMode::NONE, [cache_key](http::Request &download) { if (download.ResultCode() != 200) return; std::string data; diff --git a/Core/Util/GameManager.h b/Core/Util/GameManager.h index 336a38908f..8aff30da05 100644 --- a/Core/Util/GameManager.h +++ b/Core/Util/GameManager.h @@ -91,7 +91,7 @@ private: std::string GetGameID(const Path &path) const; std::string GetPBPGameID(FileLoader *loader) const; std::string GetISOGameID(FileLoader *loader) const; - std::shared_ptr curDownload_; + std::shared_ptr curDownload_; std::shared_ptr installThread_; bool installInProgress_ = false; bool installDonePending_ = false; diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index c530fdb83d..83a6f86574 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -159,7 +159,7 @@ static Path LocalFromRemotePath(const std::string &path) { return Path(); } -static void DiscHandler(const http::Request &request, const Path &filename) { +static void DiscHandler(const http::ServerRequest &request, const Path &filename) { s64 sz = File::GetFileSize(filename); std::string range; @@ -211,7 +211,7 @@ static void DiscHandler(const http::Request &request, const Path &filename) { } } -static void HandleListing(const http::Request &request) { +static void HandleListing(const http::ServerRequest &request) { AndroidJNIThreadContext jniContext; request.WriteHttpResponseHeader("1.0", 200, -1, "text/plain"); @@ -230,7 +230,7 @@ static void HandleListing(const http::Request &request) { } } -static bool ServeDebuggerFile(const http::Request &request) { +static bool ServeDebuggerFile(const http::ServerRequest &request) { // Skip the slash at the start of the resource path. const char *filename = request.resource() + 1; if (strstr(filename, "..") != nullptr) @@ -264,13 +264,13 @@ static bool ServeDebuggerFile(const http::Request &request) { return true; } -static void RedirectToDebugger(const http::Request &request) { +static void RedirectToDebugger(const http::ServerRequest &request) { static const std::string payload = "Redirecting to debugger UI...\r\n"; request.WriteHttpResponseHeader("1.0", 301, (int)payload.size(), "text/plain", "Location: /debugger/index.html\r\n"); request.Out()->Push(payload); } -static void HandleFallback(const http::Request &request) { +static void HandleFallback(const http::ServerRequest &request) { AndroidJNIThreadContext jniContext; if (serverFlags & (int)WebServerFlags::DISCS) { @@ -296,7 +296,7 @@ static void HandleFallback(const http::Request &request) { request.Out()->Push(payload); } -static void ForwardDebuggerRequest(const http::Request &request) { +static void ForwardDebuggerRequest(const http::ServerRequest &request) { AndroidJNIThreadContext jniContext; if (serverFlags & (int)WebServerFlags::DEBUGGER) { diff --git a/UI/DevScreens.h b/UI/DevScreens.h index 776c6bb8e8..743b83af08 100644 --- a/UI/DevScreens.h +++ b/UI/DevScreens.h @@ -218,8 +218,8 @@ private: UI::EventReturn OnLoadDump(UI::EventParams &e); std::vector files_; - std::shared_ptr listing_; - std::shared_ptr dumpDownload_; + std::shared_ptr listing_; + std::shared_ptr dumpDownload_; }; void DrawProfile(UIContext &ui); diff --git a/UI/Store.cpp b/UI/Store.cpp index dbe14d46dd..7a7f91a09a 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -63,7 +63,7 @@ public: if (useIconCache && g_iconCache.MarkPending(path_)) { const char *acceptMime = "image/png, image/jpeg, image/*; q=0.9, */*; q=0.8"; - requestManager_->StartDownloadWithCallback(path_, Path(), http::ProgressBarMode::DELAYED, [&](http::Download &download) { + requestManager_->StartDownloadWithCallback(path_, Path(), http::ProgressBarMode::DELAYED, [&](http::Request &download) { if (download.ResultCode() == 200) { std::string data; download.buffer().TakeAll(&data); @@ -98,7 +98,7 @@ public: const std::string &GetFilename() const { return path_; } private: - void DownloadCompletedCallback(http::Download &download); + void DownloadCompletedCallback(http::Request &download); bool canFocus_ = false; bool useIconCache_ = false; @@ -106,7 +106,7 @@ private: uint32_t color_ = 0xFFFFFFFF; UI::ImageSizeMode sizeMode_; http::RequestManager *requestManager_; - std::shared_ptr download_; + std::shared_ptr download_; std::string textureData_; std::unique_ptr texture_; @@ -155,7 +155,7 @@ void HttpImageFileView::SetFilename(std::string filename) { } } -void HttpImageFileView::DownloadCompletedCallback(http::Download &download) { +void HttpImageFileView::DownloadCompletedCallback(http::Request &download) { if (download.IsCancelled()) { // We were probably destroyed. Can't touch "this" (heh). return; diff --git a/UI/Store.h b/UI/Store.h index 0c4677d511..3c0be4668d 100644 --- a/UI/Store.h +++ b/UI/Store.h @@ -78,8 +78,8 @@ private: std::string GetTranslatedString(const json::JsonGet json, std::string key, const char *fallback = nullptr) const; - std::shared_ptr listing_; - std::shared_ptr image_; + std::shared_ptr listing_; + std::shared_ptr image_; // TODO: Replace with a PathBrowser or similar. Though that one only supports // local filesystems at the moment. From 8f6662aa55121f5b01147c892436ef53928cd1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 21 Jul 2023 23:13:48 +0200 Subject: [PATCH 034/167] Fix an initialLayout issue with the image we use as the backbuffers's depth --- Common/GPU/Vulkan/VulkanFramebuffer.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Common/GPU/Vulkan/VulkanFramebuffer.cpp b/Common/GPU/Vulkan/VulkanFramebuffer.cpp index e9c68794a5..975dab04bd 100644 --- a/Common/GPU/Vulkan/VulkanFramebuffer.cpp +++ b/Common/GPU/Vulkan/VulkanFramebuffer.cpp @@ -270,6 +270,12 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas bool multiview = RenderPassTypeHasMultiView(rpType); bool multisample = RenderPassTypeHasMultisample(rpType); + _dbg_assert_(!(isBackbuffer && multisample)); + + if (isBackbuffer) { + _dbg_assert_(key.depthLoadAction == VKRRenderPassLoadAction::CLEAR); + } + if (multiview) { // TODO: Assert that the device has multiview support enabled. } @@ -296,7 +302,7 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas attachments[attachmentCount].storeOp = ConvertStoreAction(key.depthStoreAction); attachments[attachmentCount].stencilLoadOp = multisample ? VK_ATTACHMENT_LOAD_OP_DONT_CARE : ConvertLoadAction(key.stencilLoadAction); attachments[attachmentCount].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction); - attachments[attachmentCount].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + attachments[attachmentCount].initialLayout = isBackbuffer ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; attachments[attachmentCount].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; attachmentCount++; } From fbf10e132b7cd85820fc1dfcc0229981cc95498e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 21 Jul 2023 23:50:07 +0200 Subject: [PATCH 035/167] Allow entering an exact Mac address, while keeping the randomization ability Fixes #16546 --- UI/GameSettingsScreen.cpp | 70 +++++++++++++++++++++++++++------------ UI/GameSettingsScreen.h | 3 -- assets/lang/en_US.ini | 1 + 3 files changed, 49 insertions(+), 25 deletions(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index d8c40d2f54..7c99613765 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -735,6 +735,53 @@ void GameSettingsScreen::CreateControlsSettings(UI::ViewGroup *controlsSettings) } } +// Compound view just like the audio file choosers +class MacAddressChooser : public UI::LinearLayout { +public: + MacAddressChooser(Path gamePath, std::string *value, const std::string &title, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr); +}; + +static constexpr UI::Size ITEM_HEIGHT = 64.f; + +MacAddressChooser::MacAddressChooser(Path gamePath_, std::string *value, const std::string &title, ScreenManager *screenManager, UI::LayoutParams *layoutParams) : UI::LinearLayout(UI::ORIENT_HORIZONTAL, layoutParams) { + using namespace UI; + SetSpacing(5.0f); + if (!layoutParams) { + layoutParams_->width = FILL_PARENT; + layoutParams_->height = ITEM_HEIGHT; + } + auto n = GetI18NCategory(I18NCat::NETWORKING); + + std::string initialValue = *value; + Add(new PopupTextInputChoice(value, title, g_Config.sMACAddress, 17, screenManager, new LinearLayoutParams(1.0f)))->OnChange.Add([=](UI::EventParams &e) { + // Validate the chosen address, and restore to initialValue if bad. + if (g_Config.sMACAddress.size() != 17) { + // TODO: Alert the user + *value = initialValue; + } + return UI::EVENT_DONE; + }); + Add(new Choice(n->T("Randomize"), new LinearLayoutParams(WRAP_CONTENT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) { + auto n = GetI18NCategory(I18NCat::NETWORKING); + auto di = GetI18NCategory(I18NCat::DIALOG); + + const char *confirmMessage = n->T("ChangeMacSaveConfirm", "Generate a new MAC address?"); + const char *warningMessage = n->T("ChangeMacSaveWarning", "Some games verify the MAC address when loading savedata, so this may break old saves."); + std::string combined = g_Config.sMACAddress + "\n\n" + std::string(confirmMessage) + "\n\n" + warningMessage; + + auto confirmScreen = new PromptScreen( + gamePath_, + combined, di->T("Yes"), di->T("No"), + [&](bool success) { + if (success) { + g_Config.sMACAddress = CreateRandMAC(); + }} + ); + screenManager->push(confirmScreen); + return UI::EVENT_DONE; + }); +} + void GameSettingsScreen::CreateNetworkingSettings(UI::ViewGroup *networkingSettings) { using namespace UI; @@ -746,7 +793,7 @@ void GameSettingsScreen::CreateNetworkingSettings(UI::ViewGroup *networkingSetti networkingSettings->Add(new Choice(n->T("Open PPSSPP Multiplayer Wiki Page")))->OnClick.Handle(this, &GameSettingsScreen::OnAdhocGuides); networkingSettings->Add(new CheckBox(&g_Config.bEnableWlan, n->T("Enable networking", "Enable networking/wlan (beta)"))); - networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.sMACAddress, n->T("Change Mac Address"), I18NCat::NONE))->OnClick.Handle(this, &GameSettingsScreen::OnChangeMacAddress); + networkingSettings->Add(new MacAddressChooser(gamePath_, &g_Config.sMACAddress, n->T("Change Mac Address"), screenManager())); static const char* wlanChannels[] = { "Auto", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11" }; auto wlanChannelChoice = networkingSettings->Add(new PopupMultiChoice(&g_Config.iWlanAdhocChannel, n->T("WLAN Channel"), wlanChannels, 0, ARRAY_SIZE(wlanChannels), I18NCat::NETWORKING, screenManager())); for (int i = 0; i < 4; i++) { @@ -1511,27 +1558,6 @@ UI::EventReturn GameSettingsScreen::OnChangeproAdhocServerAddress(UI::EventParam return UI::EVENT_DONE; } -UI::EventReturn GameSettingsScreen::OnChangeMacAddress(UI::EventParams &e) { - auto n = GetI18NCategory(I18NCat::NETWORKING); - auto di = GetI18NCategory(I18NCat::DIALOG); - - const char *confirmMessage = n->T("ChangeMacSaveConfirm", "Generate a new MAC address?"); - const char *warningMessage = n->T("ChangeMacSaveWarning", "Some games verify the MAC address when loading savedata, so this may break old saves."); - std::string combined = std::string(confirmMessage) + "\n\n" + warningMessage; - - auto confirmScreen = new PromptScreen( - gamePath_, - combined, di->T("Yes"), di->T("No"), - [&](bool success) { - if (success) { - g_Config.sMACAddress = CreateRandMAC(); - }} - ); - screenManager()->push(confirmScreen); - - return UI::EVENT_DONE; -} - UI::EventReturn GameSettingsScreen::OnTextureShader(UI::EventParams &e) { auto gr = GetI18NCategory(I18NCat::GRAPHICS); auto shaderScreen = new TextureShaderScreen(gr->T("Texture Shader")); diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 97a31234ff..1a46358900 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -80,7 +80,6 @@ private: UI::EventReturn OnControlMapping(UI::EventParams &e); UI::EventReturn OnCalibrateAnalogs(UI::EventParams &e); UI::EventReturn OnTouchControlLayout(UI::EventParams &e); - UI::EventReturn OnTiltTypeChange(UI::EventParams &e); UI::EventReturn OnTiltCustomize(UI::EventParams &e); // Global settings handlers @@ -96,14 +95,12 @@ private: UI::EventReturn OnChangeQuickChat4(UI::EventParams &e); UI::EventReturn OnChangeNickname(UI::EventParams &e); UI::EventReturn OnChangeproAdhocServerAddress(UI::EventParams &e); - UI::EventReturn OnChangeMacAddress(UI::EventParams &e); UI::EventReturn OnChangeBackground(UI::EventParams &e); UI::EventReturn OnFullscreenChange(UI::EventParams &e); UI::EventReturn OnFullscreenMultiChange(UI::EventParams &e); UI::EventReturn OnResolutionChange(UI::EventParams &e); UI::EventReturn OnHwScaleChange(UI::EventParams &e); UI::EventReturn OnRestoreDefaultSettings(UI::EventParams &e); - UI::EventReturn OnRenderingMode(UI::EventParams &e); UI::EventReturn OnRenderingBackend(UI::EventParams &e); UI::EventReturn OnRenderingDevice(UI::EventParams &e); UI::EventReturn OnInflightFramesChoice(UI::EventParams &e); diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 7c0999fe1d..d0cecb4147 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -836,6 +836,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center From 0ab4c58343291f4c02937c6b90ebc1406bcc0049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 22 Jul 2023 01:30:20 +0200 Subject: [PATCH 036/167] Improve handling of server/token errors --- Core/RetroAchievements.cpp | 24 ++++++++++++++++++++++-- Core/RetroAchievements.h | 4 ++++ UI/RetroAchievementScreens.cpp | 15 ++++++++++++++- assets/lang/en_US.ini | 1 + 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index 9bac7b9c5e..e3df57556b 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -77,6 +77,7 @@ std::string s_game_hash; std::set g_activeChallenges; bool g_isIdentifying = false; bool g_isLoggingIn = false; +int g_loginResult; // rc_client implementation static rc_client_t *g_rcClient; @@ -320,15 +321,20 @@ static void login_token_callback(int result, const char *error_message, rc_clien g_OSD.Show(OSDType::MESSAGE_WARNING, di->T("Failed to connect to server, check your internet connection.")); break; } - case RC_INVALID_STATE: case RC_API_FAILURE: + case RC_INVALID_STATE: case RC_MISSING_VALUE: case RC_INVALID_JSON: default: + { + auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS); ERROR_LOG(ACHIEVEMENTS, "Failure logging in via token: %d, %s", result, error_message); + g_OSD.Show(OSDType::MESSAGE_WARNING, ac->T("Failed logging in to RetroAchievements")); OnAchievementsLoginStateChange(); break; } + } + g_loginResult = result; g_isLoggingIn = false; } @@ -356,6 +362,19 @@ void Initialize() { rc_client_set_event_handler(g_rcClient, event_handler_callback); + TryLoginByToken(); +} + +bool HasToken() { + return !NativeLoadSecret(RA_TOKEN_SECRET_NAME).empty(); +} + +bool LoginProblems(std::string *errorString) { + // TODO: Set error string. + return g_loginResult != RC_OK; +} + +void TryLoginByToken() { std::string api_token = NativeLoadSecret(RA_TOKEN_SECRET_NAME); if (!api_token.empty()) { g_isLoggingIn = true; @@ -398,6 +417,7 @@ static void login_password_callback(int result, const char *error_message, rc_cl } g_OSD.RemoveProgressBar("cheevos_async_login", true, 0.1f); + g_loginResult = RC_OK; // For these, we don't want the "permanence" of the login-by-token failure, this prevents LoginProblems from returning true. g_isLoggingIn = false; } @@ -420,7 +440,7 @@ void Logout() { NativeSaveSecret(RA_TOKEN_SECRET_NAME, ""); g_Config.Save("Achievements logout"); g_activeChallenges.clear(); - + g_loginResult = RC_OK; // Allow trying again OnAchievementsLoginStateChange(); } diff --git a/Core/RetroAchievements.h b/Core/RetroAchievements.h index 5e524cc79a..81648c7b8d 100644 --- a/Core/RetroAchievements.h +++ b/Core/RetroAchievements.h @@ -82,6 +82,10 @@ rc_client_t *GetClient(); void Initialize(); void UpdateSettings(); +bool LoginProblems(std::string *errorString); +bool HasToken(); +void TryLoginByToken(); + /// Called when the system is being shut down. If Shutdown() returns false, the shutdown should be aborted if possible. bool Shutdown(); diff --git a/UI/RetroAchievementScreens.cpp b/UI/RetroAchievementScreens.cpp index 143a49c238..a181bbe7aa 100644 --- a/UI/RetroAchievementScreens.cpp +++ b/UI/RetroAchievementScreens.cpp @@ -290,7 +290,20 @@ void RetroAchievementsSettingsScreen::CreateAccountTab(UI::ViewGroup *viewGroup) return UI::EVENT_DONE; }); } else { - if (System_GetPropertyBool(SYSPROP_HAS_LOGIN_DIALOG)) { + std::string errorMessage; + if (Achievements::LoginProblems(&errorMessage)) { + viewGroup->Add(new NoticeView(NoticeLevel::WARN, ac->T("Failed logging in to RetroAchievements"), errorMessage)); + if (Achievements::HasToken()) { + viewGroup->Add(new Choice(di->T("Retry")))->OnClick.Add([=](UI::EventParams &) -> UI::EventReturn { + Achievements::TryLoginByToken(); + return UI::EVENT_DONE; + }); + } + viewGroup->Add(new Choice(di->T("Log out")))->OnClick.Add([=](UI::EventParams &) -> UI::EventReturn { + Achievements::Logout(); + return UI::EVENT_DONE; + }); + } else if (System_GetPropertyBool(SYSPROP_HAS_LOGIN_DIALOG)) { viewGroup->Add(new Choice(ac->T("Log in")))->OnClick.Add([=](UI::EventParams &) -> UI::EventReturn { System_AskUsernamePassword(ac->T("Log in"), [](const std::string &value, int) { std::vector parts; diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index d0cecb4147..ef8b09e2eb 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -30,6 +30,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... From 9305870cca01fbc9b1dcbbf9dcc2e42a7fe877a1 Mon Sep 17 00:00:00 2001 From: Hoe Hao Cheng Date: Sat, 22 Jul 2023 19:37:14 +0800 Subject: [PATCH 037/167] Fix a segfault in ReadAllWithProgress --- Common/Net/NetBuffer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/Net/NetBuffer.cpp b/Common/Net/NetBuffer.cpp index 06310a6c83..6a507977b2 100644 --- a/Common/Net/NetBuffer.cpp +++ b/Common/Net/NetBuffer.cpp @@ -76,8 +76,8 @@ bool Buffer::ReadAllWithProgress(int fd, int knownSize, RequestProgress *progres int total = 0; while (true) { bool ready = false; - while (!ready && (!progress || !*progress->cancelled)) { - if (*progress->cancelled) + while (!ready && (!progress || (progress->cancelled && !*progress->cancelled))) { + if (progress && progress->cancelled && *progress->cancelled) return false; ready = fd_util::WaitUntilReady(fd, CANCEL_INTERVAL, false); } From 556ff1e3f15259bae70074391c7a938086ef8410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Jul 2023 11:10:06 +0200 Subject: [PATCH 038/167] Add missing language strings to ini files --- Tools/langtool/Cargo.lock | 8 ++++---- assets/lang/ar_AE.ini | 4 ++++ assets/lang/az_AZ.ini | 4 ++++ assets/lang/bg_BG.ini | 4 ++++ assets/lang/ca_ES.ini | 4 ++++ assets/lang/cz_CZ.ini | 4 ++++ assets/lang/da_DK.ini | 4 ++++ assets/lang/de_DE.ini | 4 ++++ assets/lang/dr_ID.ini | 4 ++++ assets/lang/es_ES.ini | 4 ++++ assets/lang/es_LA.ini | 4 ++++ assets/lang/fa_IR.ini | 4 ++++ assets/lang/fi_FI.ini | 4 ++++ assets/lang/fr_FR.ini | 4 ++++ assets/lang/gl_ES.ini | 4 ++++ assets/lang/gr_EL.ini | 4 ++++ assets/lang/he_IL.ini | 4 ++++ assets/lang/he_IL_invert.ini | 4 ++++ assets/lang/hr_HR.ini | 4 ++++ assets/lang/hu_HU.ini | 4 ++++ assets/lang/id_ID.ini | 4 ++++ assets/lang/it_IT.ini | 4 ++++ assets/lang/ja_JP.ini | 4 ++++ assets/lang/jv_ID.ini | 4 ++++ assets/lang/ko_KR.ini | 4 ++++ assets/lang/lo_LA.ini | 4 ++++ assets/lang/lt-LT.ini | 4 ++++ assets/lang/ms_MY.ini | 4 ++++ assets/lang/nl_NL.ini | 4 ++++ assets/lang/no_NO.ini | 4 ++++ assets/lang/pl_PL.ini | 4 ++++ assets/lang/pt_BR.ini | 4 ++++ assets/lang/pt_PT.ini | 4 ++++ assets/lang/ro_RO.ini | 4 ++++ assets/lang/ru_RU.ini | 4 ++++ assets/lang/sv_SE.ini | 3 +++ assets/lang/tg_PH.ini | 4 ++++ assets/lang/th_TH.ini | 4 ++++ assets/lang/tr_TR.ini | 4 ++++ assets/lang/uk_UA.ini | 4 ++++ assets/lang/vi_VN.ini | 4 ++++ assets/lang/zh_CN.ini | 4 ++++ assets/lang/zh_TW.ini | 4 ++++ 43 files changed, 171 insertions(+), 4 deletions(-) diff --git a/Tools/langtool/Cargo.lock b/Tools/langtool/Cargo.lock index 8174dbca03..1d7ab7ee8c 100644 --- a/Tools/langtool/Cargo.lock +++ b/Tools/langtool/Cargo.lock @@ -106,18 +106,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92de25114670a878b1261c79c9f8f729fb97e95bac93f6312f583c60dd6a1dfe" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" dependencies = [ "proc-macro2", ] diff --git a/assets/lang/ar_AE.ini b/assets/lang/ar_AE.ini index 118fa5c8d6..b07c9c6dd8 100644 --- a/assets/lang/ar_AE.ini +++ b/assets/lang/ar_AE.ini @@ -16,10 +16,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -612,6 +614,7 @@ Stereo rendering = Stereo rendering Stretch = ‎المط Texture Filter = ‎منقي الرسوم Texture Filtering = ‎مفلتر الرسوم +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = ‎تكبير الرسوم Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -846,6 +849,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/az_AZ.ini b/assets/lang/az_AZ.ini index a6168a7879..5784678405 100644 --- a/assets/lang/az_AZ.ini +++ b/assets/lang/az_AZ.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = Texture filtering Texture Filtering = Texture filtering +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Texture scaling Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/bg_BG.ini b/assets/lang/bg_BG.ini index ed28402526..aaf33ac2b5 100644 --- a/assets/lang/bg_BG.ini +++ b/assets/lang/bg_BG.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = Текстурно филтриране Texture Filtering = Текстурно филтриране +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Текстурно мащабиране Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/ca_ES.ini b/assets/lang/ca_ES.ini index 3624a61b66..64b556f7d7 100644 --- a/assets/lang/ca_ES.ini +++ b/assets/lang/ca_ES.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Renderitzat estèreo Stretch = Estirar Texture Filter = Filtre de textures Texture Filtering = Filtrat de textures +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Escalat de textures Texture Shader = Shader de textura Turn off Hardware Tessellation - unsupported = Desactivant tessel·lat per maquinari: no suportat. @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/cz_CZ.ini b/assets/lang/cz_CZ.ini index e919023cf5..eba2ee51ee 100644 --- a/assets/lang/cz_CZ.ini +++ b/assets/lang/cz_CZ.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Roztažení Texture Filter = Filtr textur Texture Filtering = Filtrování textur +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Změna velikosti textur Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/da_DK.ini b/assets/lang/da_DK.ini index dd7824e918..725aee8b08 100644 --- a/assets/lang/da_DK.ini +++ b/assets/lang/da_DK.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = Texturfilter Texture Filtering = Texturfiltrering +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Texturskalering Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/de_DE.ini b/assets/lang/de_DE.ini index 49e4d31387..3a3a018adb 100644 --- a/assets/lang/de_DE.ini +++ b/assets/lang/de_DE.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Strecken Texture Filter = Texturfilter Texture Filtering = Texturfilterung +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Texturskalierung Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = "Hardware Tessellierung" ausschalten: Nicht unterstützt @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Senden Send Discord Presence information = Sende Discord "Rich Presence" Information Top Center = Mitte oben diff --git a/assets/lang/dr_ID.ini b/assets/lang/dr_ID.ini index f4cb54eab5..101166a2c1 100644 --- a/assets/lang/dr_ID.ini +++ b/assets/lang/dr_ID.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = Panyaring Texture Texture Filtering = Saring i Texture +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Petonggoi Texture Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/es_ES.ini b/assets/lang/es_ES.ini index 2fd2dc1007..d67b588cc3 100644 --- a/assets/lang/es_ES.ini +++ b/assets/lang/es_ES.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Renderizado estéreo Stretch = Estirar Texture Filter = Filtro de texturas Texture Filtering = Filtrado de texturas +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Escalado de texturas Texture Shader = Shader de textura Turn off Hardware Tessellation - unsupported = Desactivando teselado por hardware: no soportado. @@ -839,6 +842,7 @@ Quick Chat 3 = Chat rápido 3 Quick Chat 4 = Chat rápido 4 Quick Chat 5 = Chat rápido 5 QuickChat = Chat rápido +Randomize = Randomize Send = Enviar Send Discord Presence information = Enviar información a Discord "Rich Presence" Top Center = Superior centrado diff --git a/assets/lang/es_LA.ini b/assets/lang/es_LA.ini index 29dd67c212..3a93988723 100644 --- a/assets/lang/es_LA.ini +++ b/assets/lang/es_LA.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -605,6 +607,7 @@ Stereo rendering = Stereo rendering Stretch = Estirar Texture Filter = Filtro de texturas Texture Filtering = Filtrado de texturas +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Escalado de texturas Texture Shader = Shader de texturas Turn off Hardware Tessellation - unsupported = Desactivando Teselado por Hardware: no es soportado. @@ -839,6 +842,7 @@ Quick Chat 3 = Chat rápido 3 Quick Chat 4 = Chat rápido 4 Quick Chat 5 = Chat rápido 5 QuickChat = Chat rápido +Randomize = Randomize Send = Enviar Send Discord Presence information = Enviar información al Discord "Rich Presence" Top Center = Arriba diff --git a/assets/lang/fa_IR.ini b/assets/lang/fa_IR.ini index 2cd3ede07d..2d416d21c3 100644 --- a/assets/lang/fa_IR.ini +++ b/assets/lang/fa_IR.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = ‎کشیدن Texture Filter = ‎فیلتر تکسچر Texture Filtering = ‎فیلتر کردن تکسچر +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = ‎تغییر سایز تکسچر ها Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/fi_FI.ini b/assets/lang/fi_FI.ini index fe047eb8da..184ffd47a0 100644 --- a/assets/lang/fi_FI.ini +++ b/assets/lang/fi_FI.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo renderöinti Stretch = Venytä Texture Filter = Tekstuurisuodatin Texture Filtering = Tekstuurin suodatus +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Tekstuurin skaalaus Texture Shader = Tekstuurin varjostin Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/fr_FR.ini b/assets/lang/fr_FR.ini index efc59af1a6..cf2b7cac3e 100644 --- a/assets/lang/fr_FR.ini +++ b/assets/lang/fr_FR.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Étirement Texture Filter = Filtre de textures Texture Filtering = Filtrage des textures +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Mise à l'échelle des textures Texture Shader = Shader de textures Turn off Hardware Tessellation - unsupported = Veuillez désactiver "Tessellation matérielle" : non supportée @@ -838,6 +841,7 @@ Quick Chat 3 = Chat rapide 3 Quick Chat 4 = Chat rapide 4 Quick Chat 5 = Chat rapide 5 QuickChat = Chat rapide +Randomize = Randomize Send = Envoyer Send Discord Presence information = Envoyer l'information "Rich Presence" de Discord Top Center = En haut au centre diff --git a/assets/lang/gl_ES.ini b/assets/lang/gl_ES.ini index dc1b1af2ec..6d1af3d3ac 100644 --- a/assets/lang/gl_ES.ini +++ b/assets/lang/gl_ES.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = Filtro de texturas Texture Filtering = Filtrado de texturas +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Escalado de texturas Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/gr_EL.ini b/assets/lang/gr_EL.ini index 55ee37398c..3a218bdcbb 100644 --- a/assets/lang/gr_EL.ini +++ b/assets/lang/gr_EL.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Επέκταση Texture Filter = Φίλτρο Υφών Texture Filtering = Φιλτράρισμα Υφών +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Κλιμάκωση Υφών Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Απενεργοποιήστε το "Hardware Tessellation": δεν υποστηρίζεται @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Αποστολή "Rich Presence" πληροφοριών σε Discord Top Center = Top center diff --git a/assets/lang/he_IL.ini b/assets/lang/he_IL.ini index 7fcfbfc901..a548d195e2 100644 --- a/assets/lang/he_IL.ini +++ b/assets/lang/he_IL.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = קידוד טקסטורה Texture Filtering = קידוד טקסטורה +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = מידת טקסטורה Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/he_IL_invert.ini b/assets/lang/he_IL_invert.ini index 2beaef34c1..d0ad856b90 100644 --- a/assets/lang/he_IL_invert.ini +++ b/assets/lang/he_IL_invert.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = הרוטסקט דודיק Texture Filtering = הרוטסקט דודיק +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = הרוטסקט תדימ Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/hr_HR.ini b/assets/lang/hr_HR.ini index 735a976f69..b89b30aa60 100644 --- a/assets/lang/hr_HR.ini +++ b/assets/lang/hr_HR.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Rastezanje Texture Filter = Filtar tekstura Texture Filtering = Filtriranje tekstura +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Mjerenje tekstura Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Isključi "hardver mozailk": nepodržano @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Šalji Discord "Rich Presence" informaciju Top Center = Top center diff --git a/assets/lang/hu_HU.ini b/assets/lang/hu_HU.ini index 1aaec7b421..4e392a4ecb 100644 --- a/assets/lang/hu_HU.ini +++ b/assets/lang/hu_HU.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Nyújtás Texture Filter = Textúra szűrés Texture Filtering = Textúra szűrés +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Textúra nagyítás Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Kapcsold ki a "Hardveres tesszelációt": nem támogatott! @@ -838,6 +841,7 @@ Quick Chat 3 = Gyors üzenet 3 Quick Chat 4 = Gyors üzenet 4 Quick Chat 5 = Gyors üzenet 5 QuickChat = Quick chat +Randomize = Randomize Send = Küldés Send Discord Presence information = Discord "Rich Presence" információ küldése Top Center = Felül középen diff --git a/assets/lang/id_ID.ini b/assets/lang/id_ID.ini index 58f6bcdd22..60d6f27793 100644 --- a/assets/lang/id_ID.ini +++ b/assets/lang/id_ID.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Peregangan Texture Filter = Filter tekstur Texture Filtering = Pemfilteran tekstur +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Penskala tekstur Texture Shader = Tekstur shader Turn off Hardware Tessellation - unsupported = Matikan "Pengujian perangkat keras": Tidak didukung @@ -838,6 +841,7 @@ Quick Chat 3 = Obrolan cepat 3 Quick Chat 4 = Obrolan cepat 4 Quick Chat 5 = Obrolan cepat 5 QuickChat = Obrolan cepat +Randomize = Randomize Send = Kirim Send Discord Presence information = Kirim informasi "Kritik dan Saran" ke Discord Top Center = Tengah atas diff --git a/assets/lang/it_IT.ini b/assets/lang/it_IT.ini index 87a995f41d..fbfb879ae2 100644 --- a/assets/lang/it_IT.ini +++ b/assets/lang/it_IT.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -605,6 +607,7 @@ Stereo rendering = Rendering stereo Stretch = Adatta al Display Texture Filter = Filtro delle texture Texture Filtering = Filtraggio delle texture +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Scalatura delle texture Texture Shader = Shader delle texture Turn off Hardware Tessellation - unsupported = Disattivazione di "tessellazione hardware": non supportata @@ -810,6 +813,7 @@ Quick Chat 3 = Chat rapida 3 Quick Chat 4 = Chat rapida 4 Quick Chat 5 = Chat rapida 5 QuickChat = Chat rapida +Randomize = Randomize Send = Invia Send Discord Presence information = Invia informazioni Discord "Rich Presence" Top Center = In alto al centro diff --git a/assets/lang/ja_JP.ini b/assets/lang/ja_JP.ini index fce86c9ebf..967d5676f9 100644 --- a/assets/lang/ja_JP.ini +++ b/assets/lang/ja_JP.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = ステレオレンダリング Stretch = 拡大 Texture Filter = テクスチャフィルタ Texture Filtering = テクスチャフィルタリング +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = テクスチャスケーリング Texture Shader = テクスチャシェーダ Turn off Hardware Tessellation - unsupported = 「ハードウェアテッセレーション」をオフにしてください。サポートされていません @@ -809,6 +812,7 @@ Quick Chat 3 = クイックチャット3 Quick Chat 4 = クイックチャット4 Quick Chat 5 = クイックチャット5 QuickChat = クイックチャット +Randomize = Randomize Send = 送信する Send Discord Presence information = Discordのプレゼンス情報を送信する Top Center = 上中央 diff --git a/assets/lang/jv_ID.ini b/assets/lang/jv_ID.ini index a167ea56e6..d163ed633f 100644 --- a/assets/lang/jv_ID.ini +++ b/assets/lang/jv_ID.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = Penyaring Tekstur Texture Filtering = Penyaring Tekstur +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Skala Tekstur Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/ko_KR.ini b/assets/lang/ko_KR.ini index f6885ee161..3ab37eba0c 100644 --- a/assets/lang/ko_KR.ini +++ b/assets/lang/ko_KR.ini @@ -9,10 +9,12 @@ Achievements = 성과 Achievements are disabled = 성과 비활성화 Challenge Mode = 도전 모드 Challenge Mode (no savestates) = 도전 모드 (저장 상태 없음) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = %d 의 %d 성과 및 %d 의 %d 포인트 획득 Encore Mode = 앙코르 모드 How to use RetroAchievements = RetroAchievements 사용 방법 +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = 순위표 점수 제출 Leaderboard submission is enabled = 순위표 제출 활성화 Leaderboards = 순위표 @@ -605,6 +607,7 @@ Stereo rendering = 스트레오 렌더링 Stretch = 늘이기 Texture Filter = 텍스처 필터링 Texture Filtering = 텍스처 필터링 +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = 텍스처 스케일링 Texture Shader = 텍스처 쉐이더 Turn off Hardware Tessellation - unsupported = "하드웨어 조각화" 끄기: 지원되지 않음 @@ -810,6 +813,7 @@ Quick Chat 3 = 빠른 채팅 3 Quick Chat 4 = 빠른 채팅 4 Quick Chat 5 = 빠른 채팅 5 QuickChat = 빠른 채팅 +Randomize = Randomize Send = 보내기 Send Discord Presence information = 디스코드 "리치 프레즌스" 정보 보내기 Top Center = 상단 중앙 diff --git a/assets/lang/lo_LA.ini b/assets/lang/lo_LA.ini index 72106d8c7a..8a623f9cbc 100644 --- a/assets/lang/lo_LA.ini +++ b/assets/lang/lo_LA.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = ດຶງເຕັມຈໍ Texture Filter = ໂຕຕອງພື້ນຜິວ Texture Filtering = ຕອງພື້ນຜິວ +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = ປັບພື້ນຜິວ Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/lt-LT.ini b/assets/lang/lt-LT.ini index 9a6b7b6404..c9b33bed97 100644 --- a/assets/lang/lt-LT.ini +++ b/assets/lang/lt-LT.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = Tekstūrų filtravimas Texture Filtering = Tekstūrų filtravimas +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Tekstūrų skalė Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/ms_MY.ini b/assets/lang/ms_MY.ini index f98bab69f8..46cce9368b 100644 --- a/assets/lang/ms_MY.ini +++ b/assets/lang/ms_MY.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = Penapis Tekstur Texture Filtering = Penapisan Tekstur +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Penskalaan Tekstur Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/nl_NL.ini b/assets/lang/nl_NL.ini index 18b389c4d1..3e03c0c254 100644 --- a/assets/lang/nl_NL.ini +++ b/assets/lang/nl_NL.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Uitrekken Texture Filter = Texturefilter Texture Filtering = Texturefilter +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Textures schalen Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Schakel "hardwarebetegeling" uit: niet ondersteund @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/no_NO.ini b/assets/lang/no_NO.ini index bd09ed8688..22e9c117e5 100644 --- a/assets/lang/no_NO.ini +++ b/assets/lang/no_NO.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = Tekstur filtrering Texture Filtering = Tekstur filtrering +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Teksturskalning Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/pl_PL.ini b/assets/lang/pl_PL.ini index a3007551db..559844e165 100644 --- a/assets/lang/pl_PL.ini +++ b/assets/lang/pl_PL.ini @@ -8,10 +8,12 @@ Achievements = Osiągnięcia Achievements are disabled = Achievements are disabled Challenge Mode = Tryb Wyzwania Challenge Mode (no savestates) = Tryb Wyzwania (bez zapisów stanu) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = Zdobyłeś %d z %d osiągnięć i %d z %d punktów Encore Mode = Encore Mode How to use RetroAchievements = Jak używać RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Tabele wyników @@ -608,6 +610,7 @@ Stereo rendering = Renderowanie stereoskopowe Stretch = Rozciąganie Texture Filter = Filtrowanie tekstur Texture Filtering = Filtrowanie tekstur +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Skalowanie tekstur Texture Shader = Shadery tekstur Turn off Hardware Tessellation - unsupported = Wyłącz sprzętową teselację: opcja nie jest wspierana @@ -842,6 +845,7 @@ Quick Chat 3 = Szybki czat 3 Quick Chat 4 = Szybki czat 4 Quick Chat 5 = Szybki czat 5 QuickChat = Szybki czat +Randomize = Randomize Send = Wyślij Send Discord Presence information = Przesyłaj informacje Discord Rich Presence Top Center = Górny środek diff --git a/assets/lang/pt_BR.ini b/assets/lang/pt_BR.ini index 4f48b855e5..d85e286de5 100644 --- a/assets/lang/pt_BR.ini +++ b/assets/lang/pt_BR.ini @@ -32,10 +32,12 @@ Achievements = Conquistas Achievements are disabled = Achievements are disabled Challenge Mode = Modo de Desafio Challenge Mode (no savestates) = Modo de Desafio (sem save states) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = Você ganhou %d de %d conquistas e %d de %d pontos Encore Mode = Encore Mode How to use RetroAchievements = Como usar RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = A submissão a tabela de classificação está ativada Leaderboards = Tabelas de Classificação @@ -628,6 +630,7 @@ Stereo rendering = Renderização do estéreo Stretch = Esticar Texture Filter = Filtragem das texturas Texture Filtering = Filtragem das texturas +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Dimensionamento das texturas Texture Shader = Shader das texturas Turn off Hardware Tessellation - unsupported = Desliga a "Tesselação por hardware": não suportado. @@ -833,6 +836,7 @@ Quick Chat 3 = Bate-papo rápido 3 Quick Chat 4 = Bate-papo rápido 4 Quick Chat 5 = Bate-papo rápido 5 QuickChat = Bate-papo rápido +Randomize = Randomize Send = Enviar Send Discord Presence information = Enviar informação da "Presença Rica" no Discord Top Center = Centro superior diff --git a/assets/lang/pt_PT.ini b/assets/lang/pt_PT.ini index 487416f978..52e7048678 100644 --- a/assets/lang/pt_PT.ini +++ b/assets/lang/pt_PT.ini @@ -32,10 +32,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -628,6 +630,7 @@ Stereo rendering = Stereo rendering Stretch = Esticamento Texture Filter = Filtração das texturas Texture Filtering = Filtração das texturas +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Dimensionamento das texturas Texture Shader = Shader das texturas Turn off Hardware Tessellation - unsupported = Desliga a "Tesselação por hardware": não suportado. @@ -833,6 +836,7 @@ Quick Chat 3 = Bate-papo rápido 3 Quick Chat 4 = Bate-papo rápido 4 Quick Chat 5 = Bate-papo rápido 5 QuickChat = Bate-papo rápido +Randomize = Randomize Send = Enviar Send Discord Presence information = Enviar informação de "Presença Rica" no Discord Top Center = Centro superior diff --git a/assets/lang/ro_RO.ini b/assets/lang/ro_RO.ini index 21c2ff79f7..37210aaf32 100644 --- a/assets/lang/ro_RO.ini +++ b/assets/lang/ro_RO.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -605,6 +607,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = Filtru texturi Texture Filtering = Filtrare texturi +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Scalare texturi Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -839,6 +842,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/ru_RU.ini b/assets/lang/ru_RU.ini index 9f6c0ad72c..c6d6e60756 100644 --- a/assets/lang/ru_RU.ini +++ b/assets/lang/ru_RU.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Стереорендеринг Stretch = Растягивание Texture Filter = Текстурный фильтр Texture Filtering = Фильтрация текстур +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Масштабирование текстур Texture Shader = Текстурный шейдер Turn off Hardware Tessellation - unsupported = Выключите настройку "Аппаратная тесселяция": не поддерживается @@ -809,6 +812,7 @@ Quick Chat 3 = Быстрый чат 3 Quick Chat 4 = Быстрый чат 4 Quick Chat 5 = Быстрый чат 5 QuickChat = Быстрый чат +Randomize = Randomize Send = Отправить Send Discord Presence information = Отправлять информацию об игре в Discord Top Center = Вверху в центре diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index c0131f1b3b..dd07d4f809 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -8,6 +8,7 @@ Achievements = Achievements Achievements are disabled = Achievements är avstängda Challenge Mode = Utmanings-läge Challenge Mode (no savestates) = Utmanings-läge (inga sparade state) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Inställningar Earned = Du har tjänat %d av %d achievements, och %d of %d poäng Encore Mode = Encore-läge (kan ta achievements igen) @@ -606,6 +607,7 @@ Stereo rendering = Stereo-rendering Stretch = Sträck ut Texture Filter = Texturfilter Texture Filtering = Texturfiltrering +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Texturskalning Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported @@ -811,6 +813,7 @@ Quick Chat 3 = Snabbchat 3 Quick Chat 4 = Snabbchat 4 Quick Chat 5 = Snabbchat 5 QuickChat = Snabbchat +Randomize = Randomize Send = Skicka Send Discord Presence information = Visa Discord "Rich Presence"-information Top Center = Top center diff --git a/assets/lang/tg_PH.ini b/assets/lang/tg_PH.ini index 30db7e95d5..4aa1d4cd50 100644 --- a/assets/lang/tg_PH.ini +++ b/assets/lang/tg_PH.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Iunat Texture Filter = Panala ng Texture Texture Filtering = Pagsala sa Texture +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Pagsukat sa Texture Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": hindi suportado @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/th_TH.ini b/assets/lang/th_TH.ini index 6059949d5f..fb9fe10791 100644 --- a/assets/lang/th_TH.ini +++ b/assets/lang/th_TH.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -605,6 +607,7 @@ Stereo rendering = การแสดงผลแบบสามมิติ Stretch = ดึงเต็มจอ Texture Filter = ตัวกรองพื้นผิว Texture Filtering = การกรองพื้นผิว +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = การปรับสเกลพื้นผิว Texture Shader = โทนแสงสีพื้นผิว Turn off Hardware Tessellation - unsupported = ปิด "ฮาร์ดแวร์ เทสเซลเลชั่น" แล้ว เนื่องจากไม่สนับสนุนการทำงาน @@ -839,6 +842,7 @@ Quick Chat 3 = แชทด่วน 3 Quick Chat 4 = แชทด่วน 4 Quick Chat 5 = แชทด่วน 5 QuickChat = แชทด่วน +Randomize = Randomize Send = ส่ง Send Discord Presence information = แสดงชื่อเกมที่กำลังเล่นอยู่ไปยังแอพ Discord Top Center = มุมบนกลางจอ diff --git a/assets/lang/tr_TR.ini b/assets/lang/tr_TR.ini index 23bbc45862..6cd873b6ee 100644 --- a/assets/lang/tr_TR.ini +++ b/assets/lang/tr_TR.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -606,6 +608,7 @@ Stereo rendering = Stereo rendering Stretch = Serbest tam ekran (sünme) Texture Filter = Doku filtreleme Texture Filtering = Doku filtreleme +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Doku ölçekleme Texture Shader = Doku gölgelendiricisi Turn off Hardware Tessellation - unsupported = "Donanım mozaikleme"yi kapatın: desteklenmiyor @@ -840,6 +843,7 @@ Quick Chat 3 = Hızlı sohbet 3 Quick Chat 4 = Hızlı sohbet 4 Quick Chat 5 = Hızlı sohbet 5 QuickChat = Hızlı sohbet +Randomize = Randomize Send = Gönder Send Discord Presence information = Discord "Rich Presence" bilgisi gönder Top Center = Orta üst diff --git a/assets/lang/uk_UA.ini b/assets/lang/uk_UA.ini index b1862b48b5..aa8c20c5e9 100644 --- a/assets/lang/uk_UA.ini +++ b/assets/lang/uk_UA.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Розтягнення Texture Filter = Фільтр текстур Texture Filtering = Фільтрація текстур +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Масштабування текстур Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Вимкніть "апаратна тесселляція": не підтримується @@ -838,6 +841,7 @@ Quick Chat 3 = Швидкий чат 3 Quick Chat 4 = Швидкий чат 4 Quick Chat 5 = Швидкий чат 5 QuickChat = Швидкий чат +Randomize = Randomize Send = Надіслати Send Discord Presence information = Відправляти інформацію про гру в Discord Top Center = Top center diff --git a/assets/lang/vi_VN.ini b/assets/lang/vi_VN.ini index eb99b7c0e2..174e5b32b5 100644 --- a/assets/lang/vi_VN.ini +++ b/assets/lang/vi_VN.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = Stereo rendering Stretch = Stretch Texture Filter = Bộ lọc họa tiết Texture Filtering = Lọc texture +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = Họa tiết rộng Texture Shader = Texture shader Turn off Hardware Tessellation - unsupported = Tắt "phần cứng tessellation": không hổ trợ @@ -838,6 +841,7 @@ Quick Chat 3 = Quick chat 3 Quick Chat 4 = Quick chat 4 Quick Chat 5 = Quick chat 5 QuickChat = Quick chat +Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information Top Center = Top center diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index 84e8ce7aaa..0b7658679c 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -8,10 +8,12 @@ Achievements = Achievements Achievements are disabled = Achievements are disabled Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = You have earned %d of %d achievements, and %d of %d points Encore Mode = Encore Mode How to use RetroAchievements = How to use RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Leaderboard submission is enabled Leaderboards = Leaderboards @@ -604,6 +606,7 @@ Stereo rendering = 立体渲染 Stretch = 拉伸 Texture Filter = 纹理过滤方法 Texture Filtering = 纹理过滤 +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = 纹理缩放 Texture Shader = 纹理着色器 Turn off Hardware Tessellation - unsupported = 关闭"硬件曲面细分":不支持 @@ -839,6 +842,7 @@ Quick Chat 3 = 快速聊天3 Quick Chat 4 = 快速聊天4 Quick Chat 5 = 快速聊天5 QuickChat = 快速聊天 +Randomize = Randomize Send = 发送 Send Discord Presence information = 发送 Discord 在线状态 Top Center = 正上方 diff --git a/assets/lang/zh_TW.ini b/assets/lang/zh_TW.ini index 52a9b85a4c..36c018a93c 100644 --- a/assets/lang/zh_TW.ini +++ b/assets/lang/zh_TW.ini @@ -8,10 +8,12 @@ Achievements = 成就 Achievements are disabled = Achievements are disabled Challenge Mode = 挑戰模式 Challenge Mode (no savestates) = 挑戰模式 (無存檔) +Contacting RetroAchievements server... = Contacting RetroAchievements server... Customize = Customize Earned = 您已經取得 %d/%d 個成就,以及 %d/%d 個點數 Encore Mode = Encore Mode How to use RetroAchievements = 如何使用 RetroAchievements +In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = 排行榜提交已啟用 Leaderboards = 排行榜 @@ -604,6 +606,7 @@ Stereo rendering = 立體轉譯 Stretch = 延伸 Texture Filter = 紋理濾鏡 Texture Filtering = 紋理濾鏡 +Texture replacement pack activated = Texture replacement pack activated Texture Scaling = 紋理縮放 Texture Shader = 紋理著色器 Turn off Hardware Tessellation - unsupported = 關閉「硬體鑲嵌」:不支援 @@ -809,6 +812,7 @@ Quick Chat 3 = 快速聊天 3 Quick Chat 4 = 快速聊天 4 Quick Chat 5 = 快速聊天 5 QuickChat = 快速聊天 +Randomize = Randomize Send = 傳送 Send Discord Presence information = 傳送 Discord「Rich Presence」資訊 Top Center = 正上 From aa46d3aae3de6d997dee0ac0d99a993879a723cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Jul 2023 11:04:42 +0200 Subject: [PATCH 039/167] UI: CollapsibleHeader no longer shows the open/close icon if there are no items --- Common/UI/View.cpp | 4 +++- Common/UI/View.h | 4 ++++ Common/UI/ViewGroup.cpp | 11 ++++++++--- Common/UI/ViewGroup.h | 5 +++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index 9495334990..82c2497a7a 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -618,7 +618,9 @@ void CollapsibleHeader::Draw(UIContext &dc) { dc.SetFontStyle(dc.theme->uiFontSmall); dc.DrawText(text_.c_str(), bounds_.x + 4 + xoff, bounds_.centerY(), dc.theme->headerStyle.fgColor, ALIGN_LEFT | ALIGN_VCENTER); dc.Draw()->DrawImageCenterTexel(dc.theme->whiteImage, bounds_.x, bounds_.y2() - 2, bounds_.x2(), bounds_.y2(), dc.theme->headerStyle.fgColor); - dc.Draw()->DrawImageRotated(ImageID("I_ARROW"), bounds_.x + 20.0f, bounds_.y + 20.0f, 1.0f, *toggle_ ? -M_PI / 2 : M_PI); + if (hasSubItems_) { + dc.Draw()->DrawImageRotated(ImageID("I_ARROW"), bounds_.x + 20.0f, bounds_.y + 20.0f, 1.0f, *toggle_ ? -M_PI / 2 : M_PI); + } } void CollapsibleHeader::GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const { diff --git a/Common/UI/View.h b/Common/UI/View.h index 52a7b5fe26..91dc739d5e 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -870,6 +870,10 @@ public: void Draw(UIContext &dc) override; void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override; void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; + + void SetHasSubitems(bool hasSubItems) { hasSubItems_ = hasSubItems; } +private: + bool hasSubItems_ = true; }; class BitCheckBox : public CheckBox { diff --git a/Common/UI/ViewGroup.cpp b/Common/UI/ViewGroup.cpp index 757192fab8..e418d63008 100644 --- a/Common/UI/ViewGroup.cpp +++ b/Common/UI/ViewGroup.cpp @@ -1183,9 +1183,9 @@ StickyChoice *ChoiceStrip::Choice(int index) { CollapsibleSection::CollapsibleSection(const std::string &title, LayoutParams *layoutParams) : LinearLayout(ORIENT_VERTICAL, layoutParams) { SetSpacing(0.0f); - CollapsibleHeader *heading = new CollapsibleHeader(&open_, title); - views_.push_back(heading); - heading->OnClick.Add([=](UI::EventParams &) { + heading_ = new CollapsibleHeader(&open_, title); + views_.push_back(heading_); + heading_->OnClick.Add([=](UI::EventParams &) { // Change the visibility of all children except the first one. // Later maybe try something more ambitious. for (size_t i = 1; i < views_.size(); i++) { @@ -1195,4 +1195,9 @@ CollapsibleSection::CollapsibleSection(const std::string &title, LayoutParams *l }); } +void CollapsibleSection::Update() { + ViewGroup::Update(); + heading_->SetHasSubitems(views_.size() > 1); +} + } // namespace UI diff --git a/Common/UI/ViewGroup.h b/Common/UI/ViewGroup.h index 2f014ac48b..310eb20766 100644 --- a/Common/UI/ViewGroup.h +++ b/Common/UI/ViewGroup.h @@ -324,12 +324,17 @@ private: std::vector tabTweens_; }; +class CollapsibleHeader; + class CollapsibleSection : public LinearLayout { public: CollapsibleSection(const std::string &title, LayoutParams *layoutParams = nullptr); + void Update() override; + private: bool open_ = true; + CollapsibleHeader *heading_; }; } // namespace UI From eb14c87a71b0f9745f63a7960a31a383b43c77d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Jul 2023 11:05:08 +0200 Subject: [PATCH 040/167] Allow configuring the game language separately from the PPSSPP UI language, with a new setting. --- Common/Data/Collections/FastVec.h | 2 +- Common/Data/Text/I18n.cpp | 1 + Common/Data/Text/I18n.h | 1 + Common/Net/HTTPClient.cpp | 2 ++ Common/Net/NetBuffer.h | 1 - Core/Config.cpp | 30 ++++++++++++++++-------------- Core/Config.h | 4 ++++ Core/Dialog/PSPDialog.cpp | 2 +- Core/HLE/sceImpose.cpp | 4 ++-- Core/HLE/sceNp2.cpp | 3 ++- Core/HLE/sceUtility.cpp | 2 +- Core/WebServer.cpp | 3 ++- UI/GameSettingsScreen.cpp | 5 +++++ UI/MiscScreens.cpp | 11 +---------- assets/lang/en_US.ini | 17 +++++++++++++++++ assets/lang/es_LA.ini | 4 +++- assets/lang/sv_SE.ini | 17 +++++++++++++++++ 17 files changed, 76 insertions(+), 33 deletions(-) diff --git a/Common/Data/Collections/FastVec.h b/Common/Data/Collections/FastVec.h index f842f5cfdb..3dab59abb8 100644 --- a/Common/Data/Collections/FastVec.h +++ b/Common/Data/Collections/FastVec.h @@ -88,7 +88,7 @@ public: T &insert(T *iter) { int pos = iter - data_; ExtendByOne(); - if (pos + 1 < size_) { + if (pos + 1 < (int)size_) { memmove(data_ + pos + 1, data_ + pos, (size_ - pos) * sizeof(T)); } return data_[pos]; diff --git a/Common/Data/Text/I18n.cpp b/Common/Data/Text/I18n.cpp index 0b0a3d2957..43b03a38b1 100644 --- a/Common/Data/Text/I18n.cpp +++ b/Common/Data/Text/I18n.cpp @@ -41,6 +41,7 @@ static const char * const g_categoryNames[(size_t)I18NCat::CATEGORY_COUNT] = { "Upgrade", "VR", "Achievements", + "PSPSettings", }; I18NRepo g_i18nrepo; diff --git a/Common/Data/Text/I18n.h b/Common/Data/Text/I18n.h index 15cdf25147..dba943fdbc 100644 --- a/Common/Data/Text/I18n.h +++ b/Common/Data/Text/I18n.h @@ -57,6 +57,7 @@ enum class I18NCat : uint8_t { UPGRADE, VR, ACHIEVEMENTS, + PSPSETTINGS, CATEGORY_COUNT, NONE = CATEGORY_COUNT, }; diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index 7c810ec975..15477558b9 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -383,6 +383,8 @@ int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector & } int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector &responseHeaders, Buffer *output, net::RequestProgress *progress) { + _dbg_assert_(progress->cancelled); + bool gzip = false; bool chunked = false; int contentLength = 0; diff --git a/Common/Net/NetBuffer.h b/Common/Net/NetBuffer.h index 32525ad3f7..b30851d12d 100644 --- a/Common/Net/NetBuffer.h +++ b/Common/Net/NetBuffer.h @@ -9,7 +9,6 @@ namespace net { class RequestProgress { public: - RequestProgress() {} explicit RequestProgress(bool *c) : cancelled(c) {} void Update(int64_t downloaded, int64_t totalBytes, bool done); diff --git a/Core/Config.cpp b/Core/Config.cpp index 820cf0e8c6..15fdecca7a 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -796,25 +796,12 @@ static const ConfigSetting networkSettings[] = { ConfigSetting("QuickChat5", &g_Config.sQuickChat4, "Quick Chat 5", CfgFlag::PER_GAME), }; -static int DefaultSystemParamLanguage() { - int defaultLang = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH; - if (g_Config.bFirstRun) { - // TODO: Be smart about same language, different country - auto &langValuesMapping = g_Config.GetLangValuesMapping(); - auto iter = langValuesMapping.find(g_Config.sLanguageIni); - if (iter != langValuesMapping.end()) { - defaultLang = iter->second.second; - } - } - return defaultLang; -} - static const ConfigSetting systemParamSettings[] = { ConfigSetting("PSPModel", &g_Config.iPSPModel, PSP_MODEL_SLIM, CfgFlag::PER_GAME | CfgFlag::REPORT), ConfigSetting("PSPFirmwareVersion", &g_Config.iFirmwareVersion, PSP_DEFAULT_FIRMWARE, CfgFlag::PER_GAME | CfgFlag::REPORT), ConfigSetting("NickName", &g_Config.sNickName, "PPSSPP", CfgFlag::PER_GAME), ConfigSetting("MacAddress", &g_Config.sMACAddress, "", CfgFlag::PER_GAME), - ConfigSetting("Language", &g_Config.iLanguage, &DefaultSystemParamLanguage, CfgFlag::PER_GAME | CfgFlag::REPORT), + ConfigSetting("GameLanguage", &g_Config.iLanguage, -1, CfgFlag::PER_GAME | CfgFlag::REPORT), ConfigSetting("ParamTimeFormat", &g_Config.iTimeFormat, PSP_SYSTEMPARAM_TIME_FORMAT_24HR, CfgFlag::PER_GAME), ConfigSetting("ParamDateFormat", &g_Config.iDateFormat, PSP_SYSTEMPARAM_DATE_FORMAT_YYYYMMDD, CfgFlag::PER_GAME), ConfigSetting("TimeZone", &g_Config.iTimeZone, 0, CfgFlag::PER_GAME), @@ -1771,3 +1758,18 @@ void Config::GetReportingInfo(UrlEncoder &data) { bool Config::IsPortrait() const { return (iInternalScreenRotation == ROTATION_LOCKED_VERTICAL || iInternalScreenRotation == ROTATION_LOCKED_VERTICAL180) && !bSkipBufferEffects; } + +int Config::GetPSPLanguage() { + if (g_Config.iLanguage == -1) { + const auto &langValuesMapping = GetLangValuesMapping(); + auto iter = langValuesMapping.find(g_Config.sLanguageIni); + if (iter != langValuesMapping.end()) { + return iter->second.second; + } else { + // Fallback to English + return PSP_SYSTEMPARAM_LANGUAGE_ENGLISH; + } + } else { + return g_Config.iLanguage; + } +} diff --git a/Core/Config.h b/Core/Config.h index d62d846a0f..73948124fb 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -569,6 +569,10 @@ public: void SetAppendedConfigIni(const Path &path); void UpdateAfterSettingAutoFrameSkip(); void NotifyUpdatedCpuCore(); + + // Applies the Auto setting if set. Returns an enum value from PSP_SYSTEMPARAM_LANGUAGE_*. + int GetPSPLanguage(); + protected: void LoadStandardControllerIni(); void LoadLangValuesMapping(); diff --git a/Core/Dialog/PSPDialog.cpp b/Core/Dialog/PSPDialog.cpp index 964f7ae301..dd05b2441c 100644 --- a/Core/Dialog/PSPDialog.cpp +++ b/Core/Dialog/PSPDialog.cpp @@ -42,7 +42,7 @@ PSPDialog::~PSPDialog() { void PSPDialog::InitCommon() { UpdateCommon(); - if (GetCommonParam() && GetCommonParam()->language != g_Config.iLanguage) { + if (GetCommonParam() && GetCommonParam()->language != g_Config.GetPSPLanguage()) { WARN_LOG(SCEUTILITY, "Game requested language %d, ignoring and using user language", GetCommonParam()->language); } } diff --git a/Core/HLE/sceImpose.cpp b/Core/HLE/sceImpose.cpp index c5ab64a24a..5cb102a8d0 100644 --- a/Core/HLE/sceImpose.cpp +++ b/Core/HLE/sceImpose.cpp @@ -40,7 +40,7 @@ static u32 backlightOffTime; void __ImposeInit() { - language = g_Config.iLanguage; + language = g_Config.GetPSPLanguage(); if (PSP_CoreParameter().compat.flags().EnglishOrJapaneseOnly) { if (language != PSP_SYSTEMPARAM_LANGUAGE_ENGLISH && language != PSP_SYSTEMPARAM_LANGUAGE_JAPANESE) { language = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH; @@ -76,7 +76,7 @@ static u32 sceImposeGetBatteryIconStatus(u32 chargingPtr, u32 iconStatusPtr) static u32 sceImposeSetLanguageMode(u32 languageVal, u32 buttonVal) { language = languageVal; buttonValue = buttonVal; - if (language != g_Config.iLanguage) { + if (language != g_Config.GetPSPLanguage()) { return hleLogWarning(SCEUTILITY, 0, "ignoring requested language"); } return hleLogSuccessI(SCEUTILITY, 0); diff --git a/Core/HLE/sceNp2.cpp b/Core/HLE/sceNp2.cpp index 89f047757f..70b349145b 100644 --- a/Core/HLE/sceNp2.cpp +++ b/Core/HLE/sceNp2.cpp @@ -131,7 +131,8 @@ static int sceNpMatching2ContextStart(int ctxId) //npMatching2Ctx.started = true; Url url("http://static-resource.np.community.playstation.net/np/resource/psp-title/" + std::string(npTitleId.data) + "_00/matching/" + std::string(npTitleId.data) + "_00-matching.xml"); http::Client client; - net::RequestProgress progress; + bool cancelled = false; + net::RequestProgress progress(&cancelled); if (!client.Resolve(url.Host().c_str(), url.Port())) { return hleLogError(SCENET, SCE_NP_COMMUNITY_SERVER_ERROR_NO_SUCH_TITLE, "HTTP failed to resolve %s", url.Resource().c_str()); } diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index 1ff229a9ca..1f559d79e5 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -898,7 +898,7 @@ static u32 sceUtilityGetSystemParamInt(u32 id, u32 destaddr) param = g_Config.bDayLightSavings?PSP_SYSTEMPARAM_DAYLIGHTSAVINGS_SAVING:PSP_SYSTEMPARAM_DAYLIGHTSAVINGS_STD; break; case PSP_SYSTEMPARAM_ID_INT_LANGUAGE: - param = g_Config.iLanguage; + param = g_Config.GetPSPLanguage(); if (PSP_CoreParameter().compat.flags().EnglishOrJapaneseOnly) { if (param != PSP_SYSTEMPARAM_LANGUAGE_ENGLISH && param != PSP_SYSTEMPARAM_LANGUAGE_JAPANESE) { param = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH; diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index 83a6f86574..a2192984c0 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -64,7 +64,8 @@ static ServerStatus RetrieveStatus() { static bool RegisterServer(int port) { bool success = false; http::Client http; - net::RequestProgress progress; + bool cancelled = false; + net::RequestProgress progress(&cancelled); Buffer theVoid = Buffer::Void(); http.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION)); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 7c99613765..377630d31c 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -897,6 +897,7 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) { auto di = GetI18NCategory(I18NCat::DIALOG); auto vr = GetI18NCategory(I18NCat::VR); auto th = GetI18NCategory(I18NCat::THEMES); + auto psps = GetI18NCategory(I18NCat::PSPSETTINGS); // TODO: Should move more into this section. systemSettings->Add(new ItemHeader(sy->T("RetroAchievements"))); auto retro = systemSettings->Add(new Choice(sy->T("RetroAchievements"))); @@ -1108,6 +1109,10 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) { }); systemSettings->Add(new ItemHeader(sy->T("PSP Settings"))); + + // The ordering here is simply mapping directly to PSP_SYSTEMPARAM_LANGUAGE_*. + static const char *defaultLanguages[] = { "Auto", "Japanese", "English", "French", "Spanish", "German", "Italian", "Dutch", "Portuguese", "Russian", "Korean", "Chinese (traditional)", "Chinese (simplified)" }; + systemSettings->Add(new PopupMultiChoice(&g_Config.iLanguage, psps->T("Game language"), defaultLanguages, -1, ARRAY_SIZE(defaultLanguages), I18NCat::PSPSETTINGS, screenManager())); static const char *models[] = { "PSP-1000", "PSP-2000/3000" }; systemSettings->Add(new PopupMultiChoice(&g_Config.iPSPModel, sy->T("PSP Model"), models, 0, ARRAY_SIZE(models), I18NCat::SYSTEM, screenManager()))->SetEnabled(!PSP_IsInited()); systemSettings->Add(new PopupTextInputChoice(&g_Config.sNickName, sy->T("Change Nickname"), "", 32, screenManager())); diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 9b30a8bdc3..0f928332e2 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -677,18 +677,9 @@ void NewLanguageScreen::OnCompleted(DialogResult result) { iniLoadedSuccessfully = g_i18nrepo.LoadIni(g_Config.sLanguageIni, langOverridePath); if (iniLoadedSuccessfully) { - // Dunno what else to do here. - auto &langValuesMapping = g_Config.GetLangValuesMapping(); - - auto iter = langValuesMapping.find(code); - if (iter == langValuesMapping.end()) { - // Fallback to English - g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH; - } else { - g_Config.iLanguage = iter->second.second; - } RecreateViews(); } else { + // Failed to load the language ini. Shouldn't really happen, but let's just switch back to the old language. g_Config.sLanguageIni = oldLang; } } diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index d0cecb4147..e96c896d27 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -977,6 +977,23 @@ Use PSP folder at root of storage = Use PSP folder at root of storage Welcome to PPSSPP! = Welcome to PPSSPP! WhatsThis = What's this? +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [RemoteISO] Browse Games = Browse games Local Server Port = Local server port diff --git a/assets/lang/es_LA.ini b/assets/lang/es_LA.ini index 3a93988723..2b9637ad9e 100644 --- a/assets/lang/es_LA.ini +++ b/assets/lang/es_LA.ini @@ -954,6 +954,8 @@ Twitter @PPSSPP_emu = Twitter @PPSSPP_emu website = Sitio web oficial written = Escrito en lenguaje C++ para una mejor portabilidad y velocidad +[PSPSettings] + [RemoteISO] Browse Games = Buscar juegos Local Server Port = Puerto del servidor local @@ -1222,7 +1224,7 @@ Reset Recording on Save/Load State = Reiniciar grabación al abrir/guardar estad Restore Default Settings = Reestablecer ajustes Rewind Snapshot Interval = Frecuencia de rebobinado\n(consume memoria) Save path in installed.txt = Guardar ruta en installed.txt -Save path in My Documents = Guardar ruta en\n"C:/Users/(tu usuario)/Documents" +Save path in My Documents = Guardar ruta en "C:/Users/(tu usuario)/Documents" Savestate Slot = Ranura de estado guardado Savestate slot backups = Copias de seguridad de estado guardado Screenshots as PNG = Capturas en PNG diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index dd07d4f809..87be54638b 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -925,6 +925,23 @@ Twitter @PPSSPP_emu = Twitter @PPSSPP_emu website = Kolla in webbsidan: written = Skriven i C++ - prestanda och portabilitet +[PSPSettings] +Auto = Auto +Chinese (simplified) = Kinesiska (förenklad) +Chinese (traditional) = Kinesiska (traditionell) +Dutch = Holländska +English = Engelska +French = Franska +Game language = Språk i spel +Games often don't support all languages = Games often don't support all languages +German = Tyska +Italian = Italienska +Japanese = Japanska +Korean = Koreanska +Portuguese = Portugisiska +Russian = Ryska +Spanish = Spanska + [MemStick] Already contains PSP data = Innehåller redan PSP-data Create or Choose a PSP folder = Skapa eller välj en PSP-mapp From d924b742c02d18d06c90457f27a87b26fe304844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Jul 2023 11:49:25 +0200 Subject: [PATCH 041/167] Make the rapid-fire interval configurable Rapid-fire works by holding down the button mapped for Rapid-Fire. Once that's held, the PSP face buttons, L, R and Start will be repeated while held. With this new setting, you can change the repeat interval from the default of 5 frames. The interval is specified as frames rather than as milliseconds or something because that often maps better to game logic. --- Core/Config.cpp | 1 + Core/Config.h | 1 + Core/HLE/sceCtrl.cpp | 25 +++++++++++++++++++++++-- Core/HLE/sceCtrl.h | 3 ++- UI/EmuScreen.cpp | 2 +- UI/GameSettingsScreen.cpp | 1 + UI/GamepadEmu.cpp | 2 +- assets/lang/en_US.ini | 2 ++ 8 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 820cf0e8c6..866edf0fd1 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -773,6 +773,7 @@ static const ConfigSetting controlSettings[] = { ConfigSetting("MouseSmoothing", &g_Config.fMouseSmoothing, 0.9f, CfgFlag::PER_GAME), ConfigSetting("SystemControls", &g_Config.bSystemControls, true, CfgFlag::DEFAULT), + ConfigSetting("RapidFileInterval", &g_Config.iRapidFireInterval, 5, CfgFlag::DEFAULT), }; static const ConfigSetting networkSettings[] = { diff --git a/Core/Config.h b/Core/Config.h index d62d846a0f..c400415ad4 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -386,6 +386,7 @@ public: float fMouseSmoothing; bool bSystemControls; + int iRapidFireInterval; // Use the hardware scaler to scale up the image to save fillrate. Similar to Windows' window size, really. int iAndroidHwScale; // 0 = device resolution. 1 = 480x272 (extended to correct aspect), 2 = 960x544 etc. diff --git a/Core/HLE/sceCtrl.cpp b/Core/HLE/sceCtrl.cpp index e2d60a723c..3c5ca06055 100644 --- a/Core/HLE/sceCtrl.cpp +++ b/Core/HLE/sceCtrl.cpp @@ -103,6 +103,8 @@ static u8 vibrationRightDropout = 160; // Not related to sceCtrl*RapidFire(), although it may do the same thing. static bool emuRapidFire = false; static u32 emuRapidFireFrames = 0; +static bool emuRapidFireToggle = true; +static u32 emuRapidFireInterval = 5; // These buttons are not affected by rapid fire (neither is analog.) const u32 CTRL_EMU_RAPIDFIRE_MASK = CTRL_UP | CTRL_DOWN | CTRL_LEFT | CTRL_RIGHT; @@ -113,7 +115,7 @@ static void __CtrlUpdateLatch() u64 t = CoreTiming::GetGlobalTimeUs(); u32 buttons = ctrlCurrent.buttons; - if (emuRapidFire && (emuRapidFireFrames % 10) < 5) + if (emuRapidFire && emuRapidFireToggle) buttons &= CTRL_EMU_RAPIDFIRE_MASK; ReplayApplyCtrl(buttons, ctrlCurrent.analog, t); @@ -173,6 +175,19 @@ u32 __CtrlPeekButtons() return ctrlCurrent.buttons; } +u32 __CtrlPeekButtonsVisual() +{ + u32 buttons; + { + std::lock_guard guard(ctrlMutex); + buttons = ctrlCurrent.buttons; + } + + if (emuRapidFire && emuRapidFireToggle) + buttons &= CTRL_EMU_RAPIDFIRE_MASK; + return buttons; +} + void __CtrlPeekAnalog(int stick, float *x, float *y) { std::lock_guard guard(ctrlMutex); @@ -206,9 +221,11 @@ void __CtrlSetAnalogXY(int stick, float x, float y) ctrlCurrent.analog[stick][CTRL_ANALOG_Y] = scaledY; } -void __CtrlSetRapidFire(bool state) +void __CtrlSetRapidFire(bool state, int interval) { emuRapidFire = state; + emuRapidFireToggle = true; + emuRapidFireInterval = interval; } bool __CtrlGetRapidFire() @@ -298,6 +315,10 @@ retry: static void __CtrlVblank() { emuRapidFireFrames++; + if (emuRapidFireFrames >= emuRapidFireInterval) { + emuRapidFireFrames = 0; + emuRapidFireToggle = !emuRapidFireToggle; + } // Reduce gamepad Vibration by set % each frame leftVibration *= (float)vibrationLeftDropout / 256.0f; diff --git a/Core/HLE/sceCtrl.h b/Core/HLE/sceCtrl.h index 02feaceb43..e633dd62de 100644 --- a/Core/HLE/sceCtrl.h +++ b/Core/HLE/sceCtrl.h @@ -73,11 +73,12 @@ void __CtrlUpdateButtons(u32 bitsToSet, u32 bitsToClear); void __CtrlSetAnalogXY(int stick, float x, float y); // Call this to enable rapid-fire. This will cause buttons other than arrows to alternate. -void __CtrlSetRapidFire(bool state); +void __CtrlSetRapidFire(bool state, int interval); bool __CtrlGetRapidFire(); // For use by internal UI like MsgDialog u32 __CtrlPeekButtons(); +u32 __CtrlPeekButtonsVisual(); // also incorporates rapid-fire void __CtrlPeekAnalog(int stick, float *x, float *y); u32 __CtrlReadLatch(); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 2d309fc6e3..d32335c663 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -764,7 +764,7 @@ void EmuScreen::onVKey(int virtualKeyCode, bool down) { } break; case VIRTKEY_RAPID_FIRE: - __CtrlSetRapidFire(down); + __CtrlSetRapidFire(down, g_Config.iRapidFireInterval); break; case VIRTKEY_MUTE_TOGGLE: if (down) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 7c99613765..7cfba245e8 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -720,6 +720,7 @@ void GameSettingsScreen::CreateControlsSettings(UI::ViewGroup *controlsSettings) settingInfo_->Show(co->T("AnalogLimiter Tip", "When the analog limiter button is pressed"), e.v); return UI::EVENT_CONTINUE; }); + controlsSettings->Add(new PopupSliderChoice(&g_Config.iRapidFireInterval, 1, 10, 5, "Rapid fire interval", screenManager(), "frames")); #if defined(USING_WIN_UI) || defined(SDL) controlsSettings->Add(new ItemHeader(co->T("Mouse", "Mouse settings"))); CheckBox *mouseControl = controlsSettings->Add(new CheckBox(&g_Config.bMouseControl, co->T("Use Mouse Control"))); diff --git a/UI/GamepadEmu.cpp b/UI/GamepadEmu.cpp index a71942c718..40524e02aa 100644 --- a/UI/GamepadEmu.cpp +++ b/UI/GamepadEmu.cpp @@ -264,7 +264,7 @@ void CustomButton::Update() { } bool PSPButton::IsDown() { - return (__CtrlPeekButtons() & pspButtonBit_) != 0; + return (__CtrlPeekButtonsVisual() & pspButtonBit_) != 0; } PSPDpad::PSPDpad(ImageID arrowIndex, const char *key, ImageID arrowDownIndex, ImageID overlayIndex, float scale, float spacing, UI::LayoutParams *layoutParams) diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index d0cecb4147..6620f03cac 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -114,6 +114,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -142,6 +143,7 @@ OnScreen = On-screen touch controls Portrait = Portrait Portrait Reversed = Portrait reversed PSP Action Buttons = PSP action buttons +Rapid fire interval = Rapid fire interval Raw input = Raw input Reset to defaults = Reset to defaults Screen Rotation = Screen rotation From 34e76e88eafaf36c994e29aeee17c21f9c78c927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Jul 2023 11:52:19 +0200 Subject: [PATCH 042/167] Copy translation lines --- assets/lang/ar_AE.ini | 2 ++ assets/lang/az_AZ.ini | 2 ++ assets/lang/bg_BG.ini | 2 ++ assets/lang/ca_ES.ini | 2 ++ assets/lang/cz_CZ.ini | 2 ++ assets/lang/da_DK.ini | 2 ++ assets/lang/de_DE.ini | 2 ++ assets/lang/dr_ID.ini | 2 ++ assets/lang/es_ES.ini | 2 ++ assets/lang/es_LA.ini | 2 ++ assets/lang/fa_IR.ini | 2 ++ assets/lang/fi_FI.ini | 2 ++ assets/lang/fr_FR.ini | 2 ++ assets/lang/gl_ES.ini | 2 ++ assets/lang/gr_EL.ini | 2 ++ assets/lang/he_IL.ini | 2 ++ assets/lang/he_IL_invert.ini | 2 ++ assets/lang/hr_HR.ini | 2 ++ assets/lang/hu_HU.ini | 2 ++ assets/lang/id_ID.ini | 2 ++ assets/lang/it_IT.ini | 2 ++ assets/lang/ja_JP.ini | 2 ++ assets/lang/jv_ID.ini | 2 ++ assets/lang/ko_KR.ini | 2 ++ assets/lang/lo_LA.ini | 2 ++ assets/lang/lt-LT.ini | 2 ++ assets/lang/ms_MY.ini | 2 ++ assets/lang/nl_NL.ini | 2 ++ assets/lang/no_NO.ini | 2 ++ assets/lang/pl_PL.ini | 2 ++ assets/lang/pt_BR.ini | 2 ++ assets/lang/pt_PT.ini | 2 ++ assets/lang/ro_RO.ini | 2 ++ assets/lang/ru_RU.ini | 2 ++ assets/lang/sv_SE.ini | 2 ++ assets/lang/tg_PH.ini | 2 ++ assets/lang/th_TH.ini | 2 ++ assets/lang/tr_TR.ini | 2 ++ assets/lang/uk_UA.ini | 2 ++ assets/lang/vi_VN.ini | 2 ++ assets/lang/zh_CN.ini | 2 ++ assets/lang/zh_TW.ini | 2 ++ 42 files changed, 84 insertions(+) diff --git a/assets/lang/ar_AE.ini b/assets/lang/ar_AE.ini index b07c9c6dd8..a9a6130c11 100644 --- a/assets/lang/ar_AE.ini +++ b/assets/lang/ar_AE.ini @@ -98,6 +98,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -126,6 +127,7 @@ OnScreen = ‎تحكم لمس الشاشة Portrait = ‎طولية Portrait Reversed = ‎طولية معكوسة PSP Action Buttons = ‎PSP أزرار الأفعال الخاصة بـ +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/az_AZ.ini b/assets/lang/az_AZ.ini index 5784678405..5ea1ea388b 100644 --- a/assets/lang/az_AZ.ini +++ b/assets/lang/az_AZ.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Ekran Kontrolları Açıq Portrait = Portrait Portrait Reversed = Portrait reversed PSP Action Buttons = PSP action buttons +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/bg_BG.ini b/assets/lang/bg_BG.ini index aaf33ac2b5..bc0b082a46 100644 --- a/assets/lang/bg_BG.ini +++ b/assets/lang/bg_BG.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Управление с докосване на екрана Portrait = Портрет Portrait Reversed = Обърнат портрет PSP Action Buttons = PSP action buttons +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/ca_ES.ini b/assets/lang/ca_ES.ini index 64b556f7d7..71950724a7 100644 --- a/assets/lang/ca_ES.ini +++ b/assets/lang/ca_ES.ini @@ -90,6 +90,7 @@ Disable diagonal input = Desactivar mov. diagonal Double tap = Doble toc Enable gesture control = Habilitar gestos Enable standard shortcut keys = Habilitar les tecles de drecera +frames = frames Gesture = Gestos Gesture mapping = Configuració de gestos Glowing borders = Vores brillants @@ -118,6 +119,7 @@ OnScreen = Controls tàctils a la pantalla Portrait = Vertical Portrait Reversed = Vertical invertit PSP Action Buttons = Botons d'acció +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Mode repetició Reset to defaults = Restaurar paràmetres diff --git a/assets/lang/cz_CZ.ini b/assets/lang/cz_CZ.ini index eba2ee51ee..d5c7a431cc 100644 --- a/assets/lang/cz_CZ.ini +++ b/assets/lang/cz_CZ.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Dotykové ovládání na obrazovce Portrait = Na výšku Portrait Reversed = Na výšku obráceně PSP Action Buttons = Tlačítka činností PSP +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/da_DK.ini b/assets/lang/da_DK.ini index 725aee8b08..1f8326bdc3 100644 --- a/assets/lang/da_DK.ini +++ b/assets/lang/da_DK.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Touch billedskærmsknapper Portrait = Portræt Portrait Reversed = Omvendt portræt PSP Action Buttons = PSP action knapper +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/de_DE.ini b/assets/lang/de_DE.ini index 3a3a018adb..21af748174 100644 --- a/assets/lang/de_DE.ini +++ b/assets/lang/de_DE.ini @@ -90,6 +90,7 @@ Disable diagonal input = Deaktiviere Diagonale Eingabe Double tap = Doppelklick Enable gesture control = Aktiviere Gestenkontrolle Enable standard shortcut keys = Aktiviere standard shortcut keys +frames = frames Gesture = Geste Gesture mapping = Gesten mapping Glowing borders = Leuchtende Ränder @@ -118,6 +119,7 @@ OnScreen = Touch Bildschirmtasten Portrait = Hochformat Portrait Reversed = Hochformat umgekehrt PSP Action Buttons = PSP Aktionstasten +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/dr_ID.ini b/assets/lang/dr_ID.ini index 101166a2c1..0ef338b827 100644 --- a/assets/lang/dr_ID.ini +++ b/assets/lang/dr_ID.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Tobolo'na jo layar sentu Portrait = Portrait Portrait Reversed = Portrait reversed PSP Action Buttons = PSP action buttons +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/es_ES.ini b/assets/lang/es_ES.ini index d67b588cc3..01a7625a08 100644 --- a/assets/lang/es_ES.ini +++ b/assets/lang/es_ES.ini @@ -90,6 +90,7 @@ Disable diagonal input = Deshabilitar mov. diagonal Double tap = Doble toque Enable gesture control = Habilitar gestos Enable standard shortcut keys = Habilitar teclas de atajo +frames = frames Gesture = Gestos Gesture mapping = Ajustes de gestos Glowing borders = Bordes brillantes @@ -118,6 +119,7 @@ OnScreen = Controles táctiles en pantalla Portrait = Vertical Portrait Reversed = Vertical invertido PSP Action Buttons = Botones de acción +Rapid fire interval = Rapid fire interval Raw input = Entrada raw Repeat mode = Repeat mode Reset to defaults = Restaurar ajustes diff --git a/assets/lang/es_LA.ini b/assets/lang/es_LA.ini index 3a93988723..6c1f4a37ce 100644 --- a/assets/lang/es_LA.ini +++ b/assets/lang/es_LA.ini @@ -91,6 +91,7 @@ Disable diagonal input = Deshabilitar mov. diagonal Double tap = Doble toque Enable gesture control = Habilitar gestos Enable standard shortcut keys = Habilitar teclas de atajo +frames = frames Gesture = Gestos Gesture mapping = Configurac. de gestos Glowing borders = Bordes brillantes @@ -119,6 +120,7 @@ OnScreen = Controles en pantalla Portrait = Retrato Portrait Reversed = Retrato invertido PSP Action Buttons = Botones de acción PSP +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Restaurar config. diff --git a/assets/lang/fa_IR.ini b/assets/lang/fa_IR.ini index 2d416d21c3..bad3d4eaa8 100644 --- a/assets/lang/fa_IR.ini +++ b/assets/lang/fa_IR.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = ‎نمایش کنترل های لمسی Portrait = ‎عمودی Portrait Reversed = ‎عمودی وارونه PSP Action Buttons = ‎اکشن دکمه های +Rapid fire interval = Rapid fire interval Raw input = ورودی راو Repeat mode = حالت تکرار Reset to defaults = ریست کردن پیشفرض diff --git a/assets/lang/fi_FI.ini b/assets/lang/fi_FI.ini index 184ffd47a0..c01c47738d 100644 --- a/assets/lang/fi_FI.ini +++ b/assets/lang/fi_FI.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Kosketusnäytön kontrollit Portrait = Portrait Portrait Reversed = Portrait reversed PSP Action Buttons = PSP action buttons +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/fr_FR.ini b/assets/lang/fr_FR.ini index cf2b7cac3e..5bf631ea38 100644 --- a/assets/lang/fr_FR.ini +++ b/assets/lang/fr_FR.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Bordures lumineuses @@ -118,6 +119,7 @@ OnScreen = Commandes tactiles Portrait = Portrait Portrait Reversed = Portrait inversé PSP Action Buttons = Boutons d'action +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/gl_ES.ini b/assets/lang/gl_ES.ini index 6d1af3d3ac..d2f8b2e83a 100644 --- a/assets/lang/gl_ES.ini +++ b/assets/lang/gl_ES.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Controis táctiles en pantalla Portrait = Vertical Portrait Reversed = Vertical invertido PSP Action Buttons = Botóns de acción +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/gr_EL.ini b/assets/lang/gr_EL.ini index 3a218bdcbb..fd27e10770 100644 --- a/assets/lang/gr_EL.ini +++ b/assets/lang/gr_EL.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Φωτιζόμενα σύνορα @@ -118,6 +119,7 @@ OnScreen = Χειριστήριο Οθόνης Αφής Portrait = Πορτραίτο Portrait Reversed = Πορτραίτο (αναστροφή) PSP Action Buttons = Πλήκτρα δράσης PSP +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/he_IL.ini b/assets/lang/he_IL.ini index a548d195e2..ca454f0dca 100644 --- a/assets/lang/he_IL.ini +++ b/assets/lang/he_IL.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = מקשי מגע על המסך Portrait = Portrait Portrait Reversed = Portrait reversed PSP Action Buttons = PSP action buttons +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/he_IL_invert.ini b/assets/lang/he_IL_invert.ini index d0ad856b90..b8f55b8db2 100644 --- a/assets/lang/he_IL_invert.ini +++ b/assets/lang/he_IL_invert.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = ךסמה לע עגמ ישקמ Portrait = Portrait Portrait Reversed = Portrait reversed PSP Action Buttons = PSP action buttons +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/hr_HR.ini b/assets/lang/hr_HR.ini index b89b30aa60..62d6452ca5 100644 --- a/assets/lang/hr_HR.ini +++ b/assets/lang/hr_HR.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Svijetleći rubovi @@ -118,6 +119,7 @@ OnScreen = Dodirne kontrole na ekranu Portrait = Portret Portrait Reversed = Obrnuti portret PSP Action Buttons = PSP akcijske kontrole +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/hu_HU.ini b/assets/lang/hu_HU.ini index 4e392a4ecb..ebef7ad57e 100644 --- a/assets/lang/hu_HU.ini +++ b/assets/lang/hu_HU.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Világító szélek @@ -118,6 +119,7 @@ OnScreen = Érintőképernyős irányítás Portrait = Álló Portrait Reversed = Fordított álló PSP Action Buttons = PSP akció gombok +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/id_ID.ini b/assets/lang/id_ID.ini index 60d6f27793..a5f36b0883 100644 --- a/assets/lang/id_ID.ini +++ b/assets/lang/id_ID.ini @@ -90,6 +90,7 @@ Disable diagonal input = Nonaktifkan masukan diagonal Double tap = Ketuk dua kali Enable gesture control = Aktifkan kontrol gerakan Enable standard shortcut keys = Aktifkan tombol pintas standar +frames = frames Gesture = Gerak Gesture mapping = Pemetaan gerakan Glowing borders = Pinggiran bercahaya @@ -118,6 +119,7 @@ OnScreen = Kontrol sentuh di layar Portrait = Tegak Portrait Reversed = Tegak terbalik PSP Action Buttons = Tombol aksi PSP +Rapid fire interval = Rapid fire interval Raw input = Masukan mentah Repeat mode = Repeat mode Reset to defaults = Atur ulang ke pengaturan awal diff --git a/assets/lang/it_IT.ini b/assets/lang/it_IT.ini index fbfb879ae2..c028a13f09 100644 --- a/assets/lang/it_IT.ini +++ b/assets/lang/it_IT.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disabilita input diagonale Double tap = Doppio tocco Enable gesture control = Abilita controllo gesti Enable standard shortcut keys = Abilita scorciatoie standard +frames = frames Gesture = Gesti Gesture mapping = Mappatura gesti Glowing borders = Bordi luminosi @@ -118,6 +119,7 @@ OnScreen = Controlli Touchscreen Portrait = Ritratto Portrait Reversed = Ritratto invertito PSP Action Buttons = Pulsanti Azione PSP (△◯✕☐) +Rapid fire interval = Rapid fire interval Raw input = Input grezzo Reset to defaults = Reset ai predefiniti Screen Rotation = Rotazione Schermo diff --git a/assets/lang/ja_JP.ini b/assets/lang/ja_JP.ini index 967d5676f9..68c14ccf05 100644 --- a/assets/lang/ja_JP.ini +++ b/assets/lang/ja_JP.ini @@ -90,6 +90,7 @@ Disable diagonal input = 斜め入力を無効にする Double tap = ダブルタップ Enable gesture control = ジェスチャー操作を有効にする Enable standard shortcut keys = 標準のショートカットキーを有効にする +frames = frames Gesture = ジェスチャー Gesture mapping = ジェスチャー操作の設定をする Glowing borders = 点灯する枠線 @@ -118,6 +119,7 @@ OnScreen = 画面にタッチ用のコントローラを表示する Portrait = 縦 Portrait Reversed = 縦 (反転) PSP Action Buttons = PSPアクションボタン +Rapid fire interval = Rapid fire interval Raw input = 現実の入力 Repeat mode = Repeat mode Reset to defaults = デフォルトに戻す diff --git a/assets/lang/jv_ID.ini b/assets/lang/jv_ID.ini index d163ed633f..eddd558383 100644 --- a/assets/lang/jv_ID.ini +++ b/assets/lang/jv_ID.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Tombol Layar-sentuh Portrait = Portret Portrait Reversed = Potret kebalik PSP Action Buttons = PSP Aksi tombol +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/ko_KR.ini b/assets/lang/ko_KR.ini index 3ab37eba0c..7797e343f0 100644 --- a/assets/lang/ko_KR.ini +++ b/assets/lang/ko_KR.ini @@ -91,6 +91,7 @@ Disable diagonal input = 대각선 입력 비활성화 Double tap = 두 번 탭 Enable gesture control = 제스처 제어 활성화 Enable standard shortcut keys = 표준 단축키 활성화 +frames = frames Gesture = 제스처 Gesture mapping = 제스처 맵핑 Glowing borders = 광선 테두리 @@ -119,6 +120,7 @@ OnScreen = 화면 터치 제어 Portrait = 세로 방향 Portrait Reversed = 세로 방향 반전 PSP Action Buttons = PSP 액션 버튼 +Rapid fire interval = Rapid fire interval Raw input = 원시 입력 Reset to defaults = 기본값으로 재설정 Screen Rotation = 화면 회전 diff --git a/assets/lang/lo_LA.ini b/assets/lang/lo_LA.ini index 8a623f9cbc..d4aaba4c02 100644 --- a/assets/lang/lo_LA.ini +++ b/assets/lang/lo_LA.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = ໃຊ້ໜ້າຈໍຄວບຄຸມແບບສຳຜັດ Portrait = ແນວຕັ້ງ Portrait Reversed = ກັບດ້ານແນວຕັ້ງ PSP Action Buttons = ປຸ່ມກົດຂອງ PSP +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/lt-LT.ini b/assets/lang/lt-LT.ini index c9b33bed97..345fb9c9d5 100644 --- a/assets/lang/lt-LT.ini +++ b/assets/lang/lt-LT.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Ekrano liečiami kontrolės mygtukai Portrait = Status Portrait Reversed = Status (apsuktas) PSP Action Buttons = PSP konsolės veiksmų mygtukai +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/ms_MY.ini b/assets/lang/ms_MY.ini index 46cce9368b..0b28dedc0e 100644 --- a/assets/lang/ms_MY.ini +++ b/assets/lang/ms_MY.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Kawalan sentuh di skrin Portrait = Portrait Portrait Reversed = Portrait reversed PSP Action Buttons = PSP action buttons +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/nl_NL.ini b/assets/lang/nl_NL.ini index 3e03c0c254..3f5ccbdee4 100644 --- a/assets/lang/nl_NL.ini +++ b/assets/lang/nl_NL.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Aanraakbesturing op het scherm Portrait = Portret Portrait Reversed = Omgekeerd portret PSP Action Buttons = PSP-actietoetsen +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/no_NO.ini b/assets/lang/no_NO.ini index 22e9c117e5..e75f417875 100644 --- a/assets/lang/no_NO.ini +++ b/assets/lang/no_NO.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Touch-kontroller på skjermen Portrait = Portrait Portrait Reversed = Portrait reversed PSP Action Buttons = PSP action buttons +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/pl_PL.ini b/assets/lang/pl_PL.ini index 559844e165..b1373e85f4 100644 --- a/assets/lang/pl_PL.ini +++ b/assets/lang/pl_PL.ini @@ -90,6 +90,7 @@ Disable diagonal input = Wyłącz wejście cyfrowe Double tap = Dwukrotne dotknięcie Enable gesture control = Włącz kontrolę kestami Enable standard shortcut keys = Włącz standardowe klawisze skrótów +frames = frames Gesture = Gesty Gesture mapping = Konfiguracja gestów Glowing borders = Świecące krawędzie @@ -118,6 +119,7 @@ OnScreen = Przyciski ekranowe Portrait = Pionowo Portrait Reversed = Odwrócone pionowo PSP Action Buttons = Klawisze akcji PSP +Rapid fire interval = Rapid fire interval Raw input = Zwykłe wejście Repeat mode = Tryb powtarzania Reset to defaults = Domyślne diff --git a/assets/lang/pt_BR.ini b/assets/lang/pt_BR.ini index d85e286de5..3fda6879c7 100644 --- a/assets/lang/pt_BR.ini +++ b/assets/lang/pt_BR.ini @@ -114,6 +114,7 @@ Disable diagonal input = Desativar entrada diagonal Double tap = Toque duplo Enable gesture control = Ativar controle dos gestos Enable standard shortcut keys = Ativar teclas padrão dos atalhos +frames = frames Gesture = Gesto Gesture mapping = Mapeamento dos gestos Glowing borders = Bordas brilhantes @@ -142,6 +143,7 @@ OnScreen = Controles do toque na tela Portrait = Retrato Portrait Reversed = Retrato invertido PSP Action Buttons = Botões de ação do PSP +Rapid fire interval = Rapid fire interval Raw input = Entrada natural dos dados Reset to defaults = Resetar para os padrões Screen Rotation = Rotação da tela diff --git a/assets/lang/pt_PT.ini b/assets/lang/pt_PT.ini index 52e7048678..9c878f7b5c 100644 --- a/assets/lang/pt_PT.ini +++ b/assets/lang/pt_PT.ini @@ -114,6 +114,7 @@ Disable diagonal input = Desativar entrada diagonal Double tap = Toque duplo Enable gesture control = Ativar controlo dos gestos Enable standard shortcut keys = Ativar teclas padrão dos atalhos +frames = frames Gesture = Gesto Gesture mapping = Mapeamento dos Gestos Glowing borders = Bordas brilhantes @@ -142,6 +143,7 @@ OnScreen = Controlos no ecrã Portrait = Retrato Portrait Reversed = Retrato invertido PSP Action Buttons = Botões de ação do PSP +Rapid fire interval = Rapid fire interval Raw input = Entrada natural dos dados Reset to defaults = Restaurar para os padrões Screen Rotation = Rotação do ecrã diff --git a/assets/lang/ro_RO.ini b/assets/lang/ro_RO.ini index 37210aaf32..a819db2a2c 100644 --- a/assets/lang/ro_RO.ini +++ b/assets/lang/ro_RO.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Controale Touch pe Ecran Portrait = Portret Portrait Reversed = Portret intors PSP Action Buttons = Butoane acțiune PSP +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/ru_RU.ini b/assets/lang/ru_RU.ini index c6d6e60756..f4e70a1dc6 100644 --- a/assets/lang/ru_RU.ini +++ b/assets/lang/ru_RU.ini @@ -90,6 +90,7 @@ Disable diagonal input = Отключить диагональный ввод Double tap = Двойное нажатие Enable gesture control = Включить настройку жестов Enable standard shortcut keys = Включить стандартные сочетания клавиш +frames = frames Gesture = Жест Gesture mapping = Настройка жестов Glowing borders = Подсветка краёв @@ -118,6 +119,7 @@ OnScreen = Элементы управления на экране Portrait = Портретная Portrait Reversed = Портретная (перевернутая) PSP Action Buttons = Кнопки действий PSP +Rapid fire interval = Rapid fire interval Raw input = Прямой ввод Repeat mode = Режим повтора Reset to defaults = По умолчанию diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index dd07d4f809..41c3c0249e 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -90,6 +90,7 @@ Disable diagonal input = Blockera diagonal input Double tap = Dubbel-tap Enable gesture control = Gest-kontroll Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gest Gesture mapping = Gest-mappning Glowing borders = Lysande kanter @@ -118,6 +119,7 @@ OnScreen = Touch-kontroller på skärmen Portrait = Porträtt Portrait Reversed = Porträtt omvänt PSP Action Buttons = PSP action-knappar +Rapid fire interval = Rapid fire interval Raw input = Rå input Repeat mode = Repetitionsläge Reset to defaults = Återställ till standardvärden diff --git a/assets/lang/tg_PH.ini b/assets/lang/tg_PH.ini index 4aa1d4cd50..6413eac3f3 100644 --- a/assets/lang/tg_PH.ini +++ b/assets/lang/tg_PH.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Glowing borders @@ -118,6 +119,7 @@ OnScreen = Pindutan sa screen Portrait = Patayo Portrait Reversed = Pabaliktad na patayo PSP Action Buttons = PSP action buttons +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/th_TH.ini b/assets/lang/th_TH.ini index fb9fe10791..aeacdc791e 100644 --- a/assets/lang/th_TH.ini +++ b/assets/lang/th_TH.ini @@ -90,6 +90,7 @@ Disable diagonal input = ปิดการใช้งานอนาล็อ Double tap = แตะสองครั้ง Enable gesture control = เปิดการควบคุมปัดหน้าจอสัมผัส Enable standard shortcut keys = เปิดการใช้งานปุ่มคีย์ลัดแบบพื้นฐาน +frames = frames Gesture = ปัดจอ Gesture mapping = เซ็ตปุ่มควบคุมปัดหน้าจอสัมผัส Glowing borders = แบบขอบทึบแสง @@ -118,6 +119,7 @@ OnScreen = ปรับใช้หน้าจอควบคุมแบบป Portrait = แนวตั้ง Portrait Reversed = แนวตั้งกลับด้าน PSP Action Buttons = ปุ่มแอ็คชั่นของ PSP +Rapid fire interval = Rapid fire interval Raw input = ค่าตั้งต้น Repeat mode = โหมดปุ่มย้ำ Reset to defaults = คืนค่าเริ่มต้น diff --git a/assets/lang/tr_TR.ini b/assets/lang/tr_TR.ini index 6cd873b6ee..ca1d1334c6 100644 --- a/assets/lang/tr_TR.ini +++ b/assets/lang/tr_TR.ini @@ -90,6 +90,7 @@ Disable diagonal input = Diyagonal girişi devre dışı bırak Double tap = Çift dokunma Enable gesture control = Hareket kontrolünü etkinleştir Enable standard shortcut keys = Standart kısayol tuşlarını etkinleştir +frames = frames Gesture = Hareket Gesture mapping = Hareket kontrollerini düzenle Glowing borders = Parlayan kenarlar @@ -118,6 +119,7 @@ OnScreen = Dokunmatik ekran kontrolleri Portrait = Dikey Portrait Reversed = Ters dikey PSP Action Buttons = PSP eylem tuşları +Rapid fire interval = Rapid fire interval Raw input = Saf girdi Repeat mode = Tekrar modu Reset to defaults = Varsayılanlara sıfırla diff --git a/assets/lang/uk_UA.ini b/assets/lang/uk_UA.ini index aa8c20c5e9..14303f74c9 100644 --- a/assets/lang/uk_UA.ini +++ b/assets/lang/uk_UA.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Тонкі границі (підсвічування) @@ -118,6 +119,7 @@ OnScreen = Елементи управління на екрані Portrait = Портретна Portrait Reversed = Портретна (перевернута) PSP Action Buttons = Кнопки дій PSP +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/vi_VN.ini b/assets/lang/vi_VN.ini index 174e5b32b5..df20414afd 100644 --- a/assets/lang/vi_VN.ini +++ b/assets/lang/vi_VN.ini @@ -90,6 +90,7 @@ Disable diagonal input = Disable diagonal input Double tap = Double tap Enable gesture control = Enable gesture control Enable standard shortcut keys = Enable standard shortcut keys +frames = frames Gesture = Gesture Gesture mapping = Gesture mapping Glowing borders = Ánh sáng đường viền @@ -118,6 +119,7 @@ OnScreen = Hiện điều khiển cảm ứng trên màn hình Portrait = Chiều dọc Portrait Reversed = Chiều dọc đảo ngược PSP Action Buttons = Nút hành động của PSP +Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode Reset to defaults = Reset to defaults diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index 0b7658679c..827ce44e25 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -90,6 +90,7 @@ Disable diagonal input = 禁用对角线方向输入 Double tap = 双击 Enable gesture control = 启用体感操作 Enable standard shortcut keys = 启用标准快捷键 +frames = frames Gesture = 体感 Gesture mapping = 体感映射 Glowing borders = 细边框(发光) @@ -118,6 +119,7 @@ OnScreen = 屏幕虚拟键 Portrait = 纵向 Portrait Reversed = 纵向反转 PSP Action Buttons = PSP 动作键 +Rapid fire interval = Rapid fire interval Raw input = 原始输入 Repeat mode = 连发模式 Reset to defaults = 重置为默认 diff --git a/assets/lang/zh_TW.ini b/assets/lang/zh_TW.ini index 36c018a93c..6067bdb027 100644 --- a/assets/lang/zh_TW.ini +++ b/assets/lang/zh_TW.ini @@ -90,6 +90,7 @@ Disable diagonal input = 停用對角線輸入 Double tap = 點兩下 Enable gesture control = 啟用手勢控制 Enable standard shortcut keys = 啟用標準快速鍵 +frames = frames Gesture = 手勢 Gesture mapping = 手勢對應 Glowing borders = 發光框線 @@ -118,6 +119,7 @@ OnScreen = 螢幕觸控控制 Portrait = 直向 Portrait Reversed = 直向反轉 PSP Action Buttons = PSP 動作按鈕 +Rapid fire interval = Rapid fire interval Raw input = 原始輸入 Reset to defaults = 重設為預設值 Screen Rotation = 螢幕旋轉 From 79cd1b958397bfef90d1ed831d2a21c6ab78253f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Jul 2023 12:10:22 +0200 Subject: [PATCH 043/167] Bump naett --- ext/naett | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/naett b/ext/naett index 24438d3d6c..3a4aa1283f 160000 --- a/ext/naett +++ b/ext/naett @@ -1 +1 @@ -Subproject commit 24438d3d6c6e4639b51237ac34cebf25781d5f0a +Subproject commit 3a4aa1283f357cc7b00b9f1019fef5f8e9f3bda2 From b118e25b6adef0024766957b304bfcfaa145bdff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Jul 2023 19:20:55 +0200 Subject: [PATCH 044/167] Vulkan: Make multithreaded rendering an option. Multi-threaded rendering is good for smoothing out performance spikes and increasing performance on low-power devices. However, the way we use it it doesn't benefit latency (input lag) at all, rather the opposite. So make it an option. --- Common/GPU/Vulkan/VulkanFrameData.cpp | 20 ++++-- Common/GPU/Vulkan/VulkanFrameData.h | 3 +- Common/GPU/Vulkan/VulkanRenderManager.cpp | 77 +++++++++++++++-------- Common/GPU/Vulkan/VulkanRenderManager.h | 4 +- Common/GPU/Vulkan/thin3d_vulkan.cpp | 10 +-- Common/GPU/thin3d_create.h | 2 +- Core/Config.cpp | 2 + Core/Config.h | 1 + SDL/SDLVulkanGraphicsContext.cpp | 3 +- UI/GameSettingsScreen.cpp | 10 +++ Windows/GPU/WindowsVulkanContext.cpp | 2 +- android/jni/AndroidVulkanContext.cpp | 2 +- assets/lang/en_US.ini | 1 + libretro/LibretroVulkanContext.cpp | 2 +- 14 files changed, 95 insertions(+), 44 deletions(-) diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp index c555a296da..472afe69a2 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.cpp +++ b/Common/GPU/Vulkan/VulkanFrameData.cpp @@ -4,6 +4,12 @@ #include "Common/Log.h" #include "Common/StringUtils.h" +#if 0 // def _DEBUG +#define VLOG(...) NOTICE_LOG(G3D, __VA_ARGS__) +#else +#define VLOG(...) +#endif + void CachedReadback::Destroy(VulkanContext *vulkan) { if (buffer) { vulkan->Delete().QueueDeleteBufferAllocation(buffer, allocation); @@ -196,12 +202,16 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame VkResult res; if (fenceToTrigger == fence) { + VLOG("Doing queue submit, fencing frame %d", this->index); // The fence is waited on by the main thread, they are not allowed to access it simultaneously. res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger); - std::lock_guard lock(fenceMutex); - readyForFence = true; - fenceCondVar.notify_one(); + if (sharedData.useMultiThreading) { + std::lock_guard lock(fenceMutex); + readyForFence = true; + fenceCondVar.notify_one(); + } } else { + VLOG("Doing queue submit, fencing something (%p)", fenceToTrigger); res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger); } @@ -219,7 +229,7 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame } } -void FrameDataShared::Init(VulkanContext *vulkan) { +void FrameDataShared::Init(VulkanContext *vulkan, bool useMultiThreading) { VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; semaphoreCreateInfo.flags = 0; VkResult res = vkCreateSemaphore(vulkan->GetDevice(), &semaphoreCreateInfo, nullptr, &acquireSemaphore); @@ -230,6 +240,8 @@ void FrameDataShared::Init(VulkanContext *vulkan) { // This fence is used for synchronizing readbacks. Does not need preinitialization. readbackFence = vulkan->CreateFence(false); vulkan->SetDebugName(readbackFence, VK_OBJECT_TYPE_FENCE, "readbackFence"); + + this->useMultiThreading = useMultiThreading; } void FrameDataShared::Destroy(VulkanContext *vulkan) { diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index 6fe679a167..63176cf469 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -53,8 +53,9 @@ struct FrameDataShared { // For synchronous readbacks. VkFence readbackFence = VK_NULL_HANDLE; + bool useMultiThreading; - void Init(VulkanContext *vulkan); + void Init(VulkanContext *vulkan, bool useMultiThreading); void Destroy(VulkanContext *vulkan); }; diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 19b106ac9d..4b64beb400 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -247,15 +247,16 @@ bool VKRComputePipeline::CreateAsync(VulkanContext *vulkan) { return true; } -VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) +VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan, bool useThread) : vulkan_(vulkan), queueRunner_(vulkan), initTimeMs_("initTimeMs"), totalGPUTimeMs_("totalGPUTimeMs"), - renderCPUTimeMs_("renderCPUTimeMs") + renderCPUTimeMs_("renderCPUTimeMs"), + useRenderThread_(useThread) { inflightFramesAtStart_ = vulkan_->GetInflightFrames(); - frameDataShared_.Init(vulkan); + frameDataShared_.Init(vulkan, useThread); for (int i = 0; i < inflightFramesAtStart_; i++) { frameData_[i].Init(vulkan, i); @@ -292,12 +293,14 @@ bool VulkanRenderManager::CreateBackbuffers() { outOfDateFrames_ = 0; - // Start the thread. + // Start the thread(s). if (HasBackbuffers()) { run_ = true; // For controlling the compiler thread's exit - INFO_LOG(G3D, "Starting Vulkan submission thread"); - thread_ = std::thread(&VulkanRenderManager::ThreadFunc, this); + if (useRenderThread_) { + INFO_LOG(G3D, "Starting Vulkan submission thread"); + thread_ = std::thread(&VulkanRenderManager::ThreadFunc, this); + } INFO_LOG(G3D, "Starting Vulkan compiler thread"); compileThread_ = std::thread(&VulkanRenderManager::CompileThreadFunc, this); } @@ -306,7 +309,8 @@ bool VulkanRenderManager::CreateBackbuffers() { // Called from main thread. void VulkanRenderManager::StopThread() { - { + if (useRenderThread_) { + _dbg_assert_(thread_.joinable()); // Tell the render thread to quit when it's done. VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::EXIT); task->frame = vulkan_->GetCurFrame(); @@ -319,7 +323,9 @@ void VulkanRenderManager::StopThread() { run_ = false; // Stop the thread. - thread_.join(); + if (useRenderThread_) { + thread_.join(); + } for (int i = 0; i < vulkan_->GetInflightFrames(); i++) { auto &frameData = frameData_[i]; @@ -492,6 +498,8 @@ void VulkanRenderManager::DrainCompileQueue() { void VulkanRenderManager::ThreadFunc() { SetCurrentThreadName("RenderMan"); while (true) { + _dbg_assert_(useRenderThread_); + // Pop a task of the queue and execute it. VKRRenderThreadTask *task = nullptr; { @@ -534,7 +542,7 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile // Makes sure the submission from the previous time around has happened. Otherwise // we are not allowed to wait from another thread here.. - { + if (useRenderThread_) { std::unique_lock lock(frameData.fenceMutex); while (!frameData.readyForFence) { frameData.fenceCondVar.wait(lock); @@ -1263,11 +1271,16 @@ void VulkanRenderManager::Finish() { VLOG("PUSH: Frame[%d]", curFrame); VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::PRESENT); task->frame = curFrame; - { + if (useRenderThread_) { std::unique_lock lock(pushMutex_); renderThreadQueue_.push(task); renderThreadQueue_.back()->steps = std::move(steps_); pushCondVar_.notify_one(); + } else { + // Just do it! + task->steps = std::move(steps_); + Run(*task); + delete task; } steps_.clear(); @@ -1348,7 +1361,7 @@ void VulkanRenderManager::Run(VKRRenderThreadTask &task) { // The submit will trigger the readbackFence, and also do the wait for it. frameData.SubmitPending(vulkan_, FrameSubmitType::Sync, frameDataShared_); - { + if (useRenderThread_) { std::unique_lock lock(syncMutex_); syncCondVar_.notify_one(); } @@ -1374,24 +1387,34 @@ void VulkanRenderManager::FlushSync() { int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; - { - VLOG("PUSH: Frame[%d]", curFrame); + if (useRenderThread_) { + { + VLOG("PUSH: Frame[%d]", curFrame); + VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::SYNC); + task->frame = curFrame; + std::unique_lock lock(pushMutex_); + renderThreadQueue_.push(task); + renderThreadQueue_.back()->steps = std::move(steps_); + pushCondVar_.notify_one(); + steps_.clear(); + } + + { + std::unique_lock lock(syncMutex_); + // Wait for the flush to be hit, since we're syncing. + while (!frameData.syncDone) { + VLOG("PUSH: Waiting for frame[%d].syncDone = 1 (sync)", curFrame); + syncCondVar_.wait(lock); + } + frameData.syncDone = false; + } + } else { VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::SYNC); task->frame = curFrame; - std::unique_lock lock(pushMutex_); - renderThreadQueue_.push(task); - renderThreadQueue_.back()->steps = std::move(steps_); - pushCondVar_.notify_one(); - } - - { - std::unique_lock lock(syncMutex_); - // Wait for the flush to be hit, since we're syncing. - while (!frameData.syncDone) { - VLOG("PUSH: Waiting for frame[%d].syncDone = 1 (sync)", curFrame); - syncCondVar_.wait(lock); - } - frameData.syncDone = false; + task->steps = std::move(steps_); + Run(*task); + delete task; + steps_.clear(); } } diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index 3343a6781a..012a098ba1 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -181,7 +181,7 @@ struct CompileQueueEntry { class VulkanRenderManager { public: - VulkanRenderManager(VulkanContext *vulkan); + VulkanRenderManager(VulkanContext *vulkan, bool useThread); ~VulkanRenderManager(); // Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again. @@ -489,6 +489,8 @@ private: bool insideFrame_ = false; bool run_ = false; + bool useRenderThread_ = true; + // This is the offset within this frame, in case of a mid-frame sync. VKRStep *curRenderStep_ = nullptr; bool curStepHasViewport_ = false; diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index db4cae6bee..325b894d17 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -384,7 +384,7 @@ class VKFramebuffer; class VKContext : public DrawContext { public: - VKContext(VulkanContext *vulkan); + VKContext(VulkanContext *vulkan, bool useRenderThread); ~VKContext(); void DebugAnnotate(const char *annotation) override; @@ -857,8 +857,8 @@ static DataFormat DataFormatFromVulkanDepth(VkFormat fmt) { return DataFormat::UNDEFINED; } -VKContext::VKContext(VulkanContext *vulkan) - : vulkan_(vulkan), renderManager_(vulkan) { +VKContext::VKContext(VulkanContext *vulkan, bool useRenderThread) + : vulkan_(vulkan), renderManager_(vulkan, useRenderThread) { shaderLanguageDesc_.Init(GLSL_VULKAN); VkFormat depthStencilFormat = vulkan->GetDeviceInfo().preferredDepthStencilFormat; @@ -1582,8 +1582,8 @@ void VKContext::Clear(int clearMask, uint32_t colorval, float depthVal, int sten renderManager_.Clear(colorval, depthVal, stencilVal, mask); } -DrawContext *T3DCreateVulkanContext(VulkanContext *vulkan) { - return new VKContext(vulkan); +DrawContext *T3DCreateVulkanContext(VulkanContext *vulkan, bool useRenderThread) { + return new VKContext(vulkan, useRenderThread); } void AddFeature(std::vector &features, const char *name, VkBool32 available, VkBool32 enabled) { diff --git a/Common/GPU/thin3d_create.h b/Common/GPU/thin3d_create.h index fccb3b076c..5f304af090 100644 --- a/Common/GPU/thin3d_create.h +++ b/Common/GPU/thin3d_create.h @@ -31,6 +31,6 @@ DrawContext *T3DCreateDX9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapt DrawContext *T3DCreateD3D11Context(ID3D11Device *device, ID3D11DeviceContext *context, ID3D11Device1 *device1, ID3D11DeviceContext1 *context1, D3D_FEATURE_LEVEL featureLevel, HWND hWnd, std::vector adapterNames); #endif -DrawContext *T3DCreateVulkanContext(VulkanContext *context); +DrawContext *T3DCreateVulkanContext(VulkanContext *context, bool useRenderThread); } // namespace Draw diff --git a/Core/Config.cpp b/Core/Config.cpp index 2f0abd782c..81b41e2646 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -611,6 +611,8 @@ static const ConfigSetting graphicsSettings[] = { ConfigSetting("InflightFrames", &g_Config.iInflightFrames, 3, CfgFlag::DEFAULT), ConfigSetting("RenderDuplicateFrames", &g_Config.bRenderDuplicateFrames, false, CfgFlag::PER_GAME), + ConfigSetting("MultiThreading", &g_Config.bRenderMultiThreading, true, CfgFlag::DEFAULT), + ConfigSetting("ShaderCache", &g_Config.bShaderCache, true, CfgFlag::DONT_SAVE), // Doesn't save. Ini-only. ConfigSetting("GpuLogProfiler", &g_Config.bGpuLogProfiler, false, CfgFlag::DEFAULT), }; diff --git a/Core/Config.h b/Core/Config.h index 721ed1b4a9..aabc73fd02 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -238,6 +238,7 @@ public: bool bGfxDebugOutput; int iInflightFrames; bool bRenderDuplicateFrames; + bool bRenderMultiThreading; // Sound bool bEnableSound; diff --git a/SDL/SDLVulkanGraphicsContext.cpp b/SDL/SDLVulkanGraphicsContext.cpp index b46858068f..270a8f30a1 100644 --- a/SDL/SDLVulkanGraphicsContext.cpp +++ b/SDL/SDLVulkanGraphicsContext.cpp @@ -136,7 +136,7 @@ bool SDLVulkanGraphicsContext::Init(SDL_Window *&window, int x, int y, int w, in return false; } - draw_ = Draw::T3DCreateVulkanContext(vulkan_); + draw_ = Draw::T3DCreateVulkanContext(vulkan_, g_Config.bRenderMultiThreading); SetGPUBackend(GPUBackend::VULKAN); bool success = draw_->CreatePresets(); _assert_(success); @@ -144,7 +144,6 @@ bool SDLVulkanGraphicsContext::Init(SDL_Window *&window, int x, int y, int w, in renderManager_ = (VulkanRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); renderManager_->SetInflightFrames(g_Config.iInflightFrames); - return true; } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index c9a90990b7..619ad77f4e 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -31,6 +31,7 @@ #include "Common/System/Display.h" // Only to check screen aspect ratio with pixel_yres/pixel_xres #include "Common/System/Request.h" +#include "Common/System/OSD.h" #include "Common/Battery/Battery.h" #include "Common/System/NativeApp.h" #include "Common/Data/Color/RGBAUtil.h" @@ -1667,6 +1668,15 @@ void DeveloperToolsScreen::CreateViews() { cpuTests->SetEnabled(TestsAvailable()); #endif + + if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) { + list->Add(new CheckBox(&g_Config.bRenderMultiThreading, dev->T("Multi-threaded rendering"), ""))->OnClick.Add([](UI::EventParams &e) { + // TODO: Not translating yet. Will combine with other translations of settings that need restart. + g_OSD.Show(OSDType::MESSAGE_WARNING, "Restart required"); + return UI::EVENT_DONE; + }); + } + // For now, we only implement GPU driver tests for Vulkan and OpenGL. This is simply // because the D3D drivers are generally solid enough to not need this type of investigation. if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN || g_Config.iGPUBackend == (int)GPUBackend::OPENGL) { diff --git a/Windows/GPU/WindowsVulkanContext.cpp b/Windows/GPU/WindowsVulkanContext.cpp index 818f398420..e4c31ab8e9 100644 --- a/Windows/GPU/WindowsVulkanContext.cpp +++ b/Windows/GPU/WindowsVulkanContext.cpp @@ -131,7 +131,7 @@ bool WindowsVulkanContext::Init(HINSTANCE hInst, HWND hWnd, std::string *error_m return false; } - draw_ = Draw::T3DCreateVulkanContext(vulkan_); + draw_ = Draw::T3DCreateVulkanContext(vulkan_, g_Config.bRenderMultiThreading); SetGPUBackend(GPUBackend::VULKAN, vulkan_->GetPhysicalDeviceProperties(deviceNum).properties.deviceName); bool success = draw_->CreatePresets(); _assert_msg_(success, "Failed to compile preset shaders"); diff --git a/android/jni/AndroidVulkanContext.cpp b/android/jni/AndroidVulkanContext.cpp index d12832a7fe..18ef569a12 100644 --- a/android/jni/AndroidVulkanContext.cpp +++ b/android/jni/AndroidVulkanContext.cpp @@ -113,7 +113,7 @@ bool AndroidVulkanContext::InitFromRenderThread(ANativeWindow *wnd, int desiredB bool success = true; if (g_Vulkan->InitSwapchain()) { - draw_ = Draw::T3DCreateVulkanContext(g_Vulkan); + draw_ = Draw::T3DCreateVulkanContext(g_Vulkan, g_Config.bRenderMultiThreading); SetGPUBackend(GPUBackend::VULKAN); success = draw_->CreatePresets(); // Doesn't fail, we ship the compiler. _assert_msg_(success, "Failed to compile preset shaders"); diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 516574a50e..458e454308 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -308,6 +308,7 @@ Log Dropped Frame Statistics = Log dropped frame statistics Log Level = Log level Log View = Log view Logging Channels = Logging channels +Multi-threaded rendering = Multi-threaded rendering Next = Next No block = No block Prev = Previous diff --git a/libretro/LibretroVulkanContext.cpp b/libretro/LibretroVulkanContext.cpp index c7072df17b..c07a000bcc 100644 --- a/libretro/LibretroVulkanContext.cpp +++ b/libretro/LibretroVulkanContext.cpp @@ -139,7 +139,7 @@ void LibretroVulkanContext::CreateDrawContext() { return; } - draw_ = Draw::T3DCreateVulkanContext(vk); + draw_ = Draw::T3DCreateVulkanContext(vk, true); ((VulkanRenderManager*)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER))->SetInflightFrames(g_Config.iInflightFrames); SetGPUBackend(GPUBackend::VULKAN); } From c1a290b41ffc9fb004da07197bb7fe5415dc6de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Jul 2023 21:52:06 +0200 Subject: [PATCH 045/167] ReplacedTexture: Bugfix D3D workaround log check --- GPU/Common/ReplacedTexture.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/ReplacedTexture.cpp b/GPU/Common/ReplacedTexture.cpp index d95ee297fb..8856282dd5 100644 --- a/GPU/Common/ReplacedTexture.cpp +++ b/GPU/Common/ReplacedTexture.cpp @@ -513,7 +513,7 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference bool bc = Draw::DataFormatIsBlockCompressed(*pixelFormat, &blockSize); _dbg_assert_(bc || *pixelFormat == Draw::DataFormat::R8G8B8A8_UNORM); - if (bc && (level.w & 3) != 0 || (level.h & 3) != 0) { + if (bc && ((level.w & 3) != 0 || (level.h & 3) != 0)) { WARN_LOG(G3D, "Block compressed replacement texture '%s' not divisible by 4x4 (%dx%d). In D3D11 (only!) we will have to expand (potentially causing glitches).", filename.c_str(), level.w, level.h); } @@ -567,7 +567,7 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference bool bc = Draw::DataFormatIsBlockCompressed(*pixelFormat, &blockSize); _dbg_assert_(bc); - if (bc && (level.w & 3) != 0 || (level.h & 3) != 0) { + if (bc && ((level.w & 3) != 0 || (level.h & 3) != 0)) { WARN_LOG(G3D, "Block compressed replacement texture '%s' not divisible by 4x4 (%dx%d). In D3D11 (only!) we will have to expand (potentially causing glitches).", filename.c_str(), level.w, level.h); } From 5cf4619c3656bd226b7e18b731802e58115378fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Jul 2023 22:52:49 +0200 Subject: [PATCH 046/167] Combine a couple of translation strings. Fix up using Google Translate (ugh) --- UI/GameSettingsScreen.cpp | 6 +++--- assets/lang/ar_AE.ini | 3 +-- assets/lang/az_AZ.ini | 3 +-- assets/lang/bg_BG.ini | 3 +-- assets/lang/ca_ES.ini | 3 +-- assets/lang/cz_CZ.ini | 3 +-- assets/lang/da_DK.ini | 3 +-- assets/lang/de_DE.ini | 3 +-- assets/lang/dr_ID.ini | 3 +-- assets/lang/en_US.ini | 3 +-- assets/lang/es_ES.ini | 3 +-- assets/lang/es_LA.ini | 3 +-- assets/lang/fa_IR.ini | 3 +-- assets/lang/fi_FI.ini | 3 +-- assets/lang/fr_FR.ini | 3 +-- assets/lang/gl_ES.ini | 3 +-- assets/lang/gr_EL.ini | 3 +-- assets/lang/he_IL.ini | 3 +-- assets/lang/he_IL_invert.ini | 3 +-- assets/lang/hr_HR.ini | 3 +-- assets/lang/hu_HU.ini | 3 +-- assets/lang/id_ID.ini | 4 ++-- assets/lang/it_IT.ini | 4 ++-- assets/lang/ja_JP.ini | 4 ++-- assets/lang/jv_ID.ini | 3 +-- assets/lang/ko_KR.ini | 4 ++-- assets/lang/lo_LA.ini | 3 +-- assets/lang/lt-LT.ini | 3 +-- assets/lang/ms_MY.ini | 3 +-- assets/lang/nl_NL.ini | 3 +-- assets/lang/no_NO.ini | 3 +-- assets/lang/pl_PL.ini | 4 ++-- assets/lang/pt_BR.ini | 4 ++-- assets/lang/pt_PT.ini | 4 ++-- assets/lang/ro_RO.ini | 3 +-- assets/lang/ru_RU.ini | 4 ++-- assets/lang/sv_SE.ini | 4 ++-- assets/lang/tg_PH.ini | 3 +-- assets/lang/th_TH.ini | 4 ++-- assets/lang/tr_TR.ini | 4 ++-- assets/lang/uk_UA.ini | 3 +-- assets/lang/vi_VN.ini | 3 +-- assets/lang/zh_CN.ini | 4 ++-- assets/lang/zh_TW.ini | 4 ++-- 44 files changed, 59 insertions(+), 89 deletions(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index c9a90990b7..41006d8114 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1450,7 +1450,7 @@ UI::EventReturn GameSettingsScreen::OnRenderingBackend(UI::EventParams &e) { // It only makes sense to show the restart prompt if the backend was actually changed. if (g_Config.iGPUBackend != (int)GetGPUBackend()) { - screenManager()->push(new PromptScreen(gamePath_, di->T("ChangingGPUBackends", "Changing GPU backends requires PPSSPP to restart. Restart now?"), di->T("Yes"), di->T("No"), + screenManager()->push(new PromptScreen(gamePath_, di->T("Changing this setting requires PPSSPP to restart."), di->T("Restart"), di->T("Cancel"), std::bind(&GameSettingsScreen::CallbackRenderingBackend, this, std::placeholders::_1))); } return UI::EVENT_DONE; @@ -1462,7 +1462,7 @@ UI::EventReturn GameSettingsScreen::OnRenderingDevice(UI::EventParams &e) { // It only makes sense to show the restart prompt if the device was actually changed. std::string *deviceNameSetting = GPUDeviceNameSetting(); if (deviceNameSetting && *deviceNameSetting != GetGPUBackendDevice()) { - screenManager()->push(new PromptScreen(gamePath_, di->T("ChangingGPUBackends", "Changing GPU backends requires PPSSPP to restart. Restart now?"), di->T("Yes"), di->T("No"), + screenManager()->push(new PromptScreen(gamePath_, di->T("Changing this setting requires PPSSPP to restart."), di->T("Restart"), di->T("Cancel"), std::bind(&GameSettingsScreen::CallbackRenderingDevice, this, std::placeholders::_1))); } return UI::EVENT_DONE; @@ -1471,7 +1471,7 @@ UI::EventReturn GameSettingsScreen::OnRenderingDevice(UI::EventParams &e) { UI::EventReturn GameSettingsScreen::OnInflightFramesChoice(UI::EventParams &e) { auto di = GetI18NCategory(I18NCat::DIALOG); if (g_Config.iInflightFrames != prevInflightFrames_) { - screenManager()->push(new PromptScreen(gamePath_, di->T("ChangingInflightFrames", "Changing graphics command buffering requires PPSSPP to restart. Restart now?"), di->T("Yes"), di->T("No"), + screenManager()->push(new PromptScreen(gamePath_, di->T("Changing this setting requires PPSSPP to restart."), di->T("Restart"), di->T("Cancel"), std::bind(&GameSettingsScreen::CallbackInflightFrames, this, std::placeholders::_1))); } return UI::EVENT_DONE; diff --git a/assets/lang/ar_AE.ini b/assets/lang/ar_AE.ini index a9a6130c11..8abd3c1b00 100644 --- a/assets/lang/ar_AE.ini +++ b/assets/lang/ar_AE.ini @@ -324,8 +324,7 @@ Active = Active Back = ‎رجوع Cancel = ‎إلغاء Center = ‎المركز -ChangingGPUBackends = ‎تغيير واجهة معالجة من كرت الشاشة يتطلب إعادة التشغيل. إعادة تشغيل الأن؟ -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = ‎إختر PPSSPP مجلد الحفظ الخاص بـ Confirm Overwrite = ‎هل تريد الكتابة علي هذه البيانات? diff --git a/assets/lang/az_AZ.ini b/assets/lang/az_AZ.ini index 5ea1ea388b..3070aee99e 100644 --- a/assets/lang/az_AZ.ini +++ b/assets/lang/az_AZ.ini @@ -316,8 +316,7 @@ Active = Active Back = Geri Cancel = Cancel Center = Center -ChangingGPUBackends = Changing GPU backends requires PPSSPP to restart. Restart now? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder Confirm Overwrite = Do you want to overwrite the data? diff --git a/assets/lang/bg_BG.ini b/assets/lang/bg_BG.ini index bc0b082a46..f14afd85b7 100644 --- a/assets/lang/bg_BG.ini +++ b/assets/lang/bg_BG.ini @@ -316,8 +316,7 @@ Active = Active Back = Назад Cancel = Отмени Center = Center -ChangingGPUBackends = Changing GPU backends requires PPSSPP to restart. Restart now? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder Confirm Overwrite = Искате ли да презапишете данните? diff --git a/assets/lang/ca_ES.ini b/assets/lang/ca_ES.ini index 71950724a7..96a7ceab5d 100644 --- a/assets/lang/ca_ES.ini +++ b/assets/lang/ca_ES.ini @@ -316,8 +316,7 @@ Active = Actiu Back = Enrere Cancel = Cancel·lar Center = Centre -ChangingGPUBackends = Canviar el motor gràfic requereix reiniciar PPSSPP.\nReiniciar ara? -ChangingInflightFrames = Canvar el búfer de comanda de gràfics requereix reiniciar PPSSPP.\nReiniciar ara? +Changing this setting requires PPSSPP to restart. = Canvar aquesta configuració requereix reiniciar PPSSPP.\nReiniciar ara? Channel: = Canal: Choose PPSSPP save folder = Tria la carpeta de guardat de PPSSPP. Confirm Overwrite = Vols sobreescriure les dades? diff --git a/assets/lang/cz_CZ.ini b/assets/lang/cz_CZ.ini index d5c7a431cc..e874f1d375 100644 --- a/assets/lang/cz_CZ.ini +++ b/assets/lang/cz_CZ.ini @@ -316,8 +316,7 @@ Active = Active Back = Zpět Cancel = Zrušit Center = Na střed -ChangingGPUBackends = Změna grafického jádra vyžaduje restart PPSSPP. Chcete tak učinit nyní?? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Zvolit složku pro ukládání dat PPSSPP Confirm Overwrite = Chcete tato data přepsat?? diff --git a/assets/lang/da_DK.ini b/assets/lang/da_DK.ini index 1f8326bdc3..b6d2e695fb 100644 --- a/assets/lang/da_DK.ini +++ b/assets/lang/da_DK.ini @@ -316,8 +316,7 @@ Active = Active Back = Tilbage Cancel = Annuler Center = Center -ChangingGPUBackends = Det er nødvendigt at genstarte PPSSPP for at ændre GPU backend. Genstart nu? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Vælg hvilken folder PPSSPP gemmer i Confirm Overwrite = Vil du overskrive dette data? diff --git a/assets/lang/de_DE.ini b/assets/lang/de_DE.ini index 21af748174..f336e671e0 100644 --- a/assets/lang/de_DE.ini +++ b/assets/lang/de_DE.ini @@ -316,8 +316,7 @@ Active = Aktiv Back = Zurück Cancel = Abbrechen Center = Zentriert -ChangingGPUBackends = Um das Grafik-Backend zu wechseln, muss PPSSPP neu gestartet werden. Wollen Sie jetzt neustarten? -ChangingInflightFrames = Das Ändern der Pufferung von Grafikbefehlen erfordert einen Neustart von PPSSPP. Wollen Sie jetzt neustarten? +Changing this setting requires PPSSPP to restart. = Wenn Sie diese Einstellung ändern muss PPSSPP neu gestartet werden. Channel: = Kanal: Choose PPSSPP save folder = Wähle PPSSPP Speicherpfad Confirm Overwrite = Wollen Sie diese Daten überschreiben? diff --git a/assets/lang/dr_ID.ini b/assets/lang/dr_ID.ini index 0ef338b827..8b62f5cc0b 100644 --- a/assets/lang/dr_ID.ini +++ b/assets/lang/dr_ID.ini @@ -316,8 +316,7 @@ Active = Active Back = Poleboko' Cancel = Ah edda pole' Center = Center -ChangingGPUBackends = Changing GPU backends requires PPSSPP to restart. Restart now? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder Confirm Overwrite = Eda na ciapa dipasi toppo' te' data? diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index cf8c64b23e..d562bfaf1d 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -341,8 +341,6 @@ Active = Active Back = Back Cancel = Cancel Center = Center -ChangingGPUBackends = Changing GPU backends requires PPSSPP to restart. Restart now? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder Confirm Overwrite = Do you want to overwrite the data? @@ -398,6 +396,7 @@ Password = Password Remove = Remove Reset = Reset Resize = Resize +Restart = Restart Retry = Retry Save = Save Save completed = Save completed. diff --git a/assets/lang/es_ES.ini b/assets/lang/es_ES.ini index 01a7625a08..8bb52d78d4 100644 --- a/assets/lang/es_ES.ini +++ b/assets/lang/es_ES.ini @@ -316,8 +316,7 @@ Active = Activo Back = Atrás Cancel = Cancelar Center = Centrar -ChangingGPUBackends = Cambiar el motor gráfico requiere reiniciar PPSSPP.\n¿Reiniciar ahora? -ChangingInflightFrames = Cambiar el búfer de comando de gráficos requiere reiniciar PPSSPP.\n¿Reiniciar ahora? +Changing this setting requires PPSSPP to restart. = Cambiar esta configuración requiere reiniciar PPSSPP.\n¿Reiniciar ahora? Channel: = Canal: Choose PPSSPP save folder = Elige la carpeta de guardado de PPSSPP. Confirm Overwrite = ¿Deseas sobreescribir los datos? diff --git a/assets/lang/es_LA.ini b/assets/lang/es_LA.ini index 0edbb74cae..4c03db9479 100644 --- a/assets/lang/es_LA.ini +++ b/assets/lang/es_LA.ini @@ -317,8 +317,7 @@ Active = Activo Back = Atrás Cancel = Cancelar Center = Centrar -ChangingGPUBackends = Cambiar el GPU requiere reiniciar PPSSPP.\n¿Reiniciar ahora? -ChangingInflightFrames = Cambiar el almacenamiento en búfer de comandos gráficos requiere que PPSSPP se reinicie.\n¿Reiniciar ahora? +Changing this setting requires PPSSPP to restart. = Cambiar esta configuración requiere que PPSSPP se reinicie.\n¿Reiniciar ahora? Channel: = Canal: Choose PPSSPP save folder = Elige la carpeta de guardado de PPSSPP. Confirm Overwrite = Hay archivos existentes,\n¿Deseas sobreescribirlos? diff --git a/assets/lang/fa_IR.ini b/assets/lang/fa_IR.ini index bad3d4eaa8..388cb8d7eb 100644 --- a/assets/lang/fa_IR.ini +++ b/assets/lang/fa_IR.ini @@ -316,8 +316,7 @@ Active = Active Back = ‎برگشت Cancel = ‎انصراف Center = وسط -ChangingGPUBackends = تغییر Backends GPU نیاز به PPSSPP برای راه اندازی مجدد. راه اندازی مجدد حالا؟ -ChangingInflightFrames = تغییر دستور گرافیک گرافیک نیاز به PPSSPP برای راه اندازی مجدد دارد. راه اندازی مجدد حالا؟ +Changing this setting requires PPSSPP to restart. = تغییر دستور گرافیک گرافیک نیاز به PPSSPP برای راه اندازی مجدد دارد. راه اندازی مجدد حالا؟ Channel: = کانال: Choose PPSSPP save folder = انتخاب پوشه برای PPSSSPP Confirm Overwrite = ‎آیا می خواهید که دیتا ها را بازنویسی کنید؟ diff --git a/assets/lang/fi_FI.ini b/assets/lang/fi_FI.ini index c01c47738d..7c40470cad 100644 --- a/assets/lang/fi_FI.ini +++ b/assets/lang/fi_FI.ini @@ -316,8 +316,7 @@ Active = Active Back = Takaisin Cancel = Cancel Center = Center -ChangingGPUBackends = Changing GPU backends requires PPSSPP to restart. Restart now? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder Confirm Overwrite = Do you want to overwrite the data? diff --git a/assets/lang/fr_FR.ini b/assets/lang/fr_FR.ini index 5bf631ea38..8bc38f564c 100644 --- a/assets/lang/fr_FR.ini +++ b/assets/lang/fr_FR.ini @@ -316,8 +316,7 @@ Active = Actif Back = Retour Cancel = Annuler Center = Centrer -ChangingGPUBackends = Changer de rendu back-end nécessite de\nredémarrer PPSSPP.\nRedémarrer maintenant ? -ChangingInflightFrames = Modifier la mise en mémoire tampon des commandes graphiques nécessite le redémarrage de PPSSPP. Redémarrer maintenant ? +Changing this setting requires PPSSPP to restart. = La modification de ce paramètre nécessite le redémarrage de PPSSPP. Redémarrer maintenant ? Channel: = Canal : Choose PPSSPP save folder = Veuillez choisir le dossier de sauvegarde de PPSSPP. Confirm Overwrite = Voulez-vous écraser les données ? diff --git a/assets/lang/gl_ES.ini b/assets/lang/gl_ES.ini index d2f8b2e83a..60cf721d73 100644 --- a/assets/lang/gl_ES.ini +++ b/assets/lang/gl_ES.ini @@ -316,8 +316,7 @@ Active = Active Back = Atrás Cancel = Cancelar Center = Center -ChangingGPUBackends = Cambiar o motor gráfico require reiniciar PPSSPP.\nReiniciar agora? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Elixe a carpeta de gardado de PPSSPP. Confirm Overwrite = Desexas sobreescribir os datos? diff --git a/assets/lang/gr_EL.ini b/assets/lang/gr_EL.ini index fd27e10770..bfc30d241a 100644 --- a/assets/lang/gr_EL.ini +++ b/assets/lang/gr_EL.ini @@ -316,8 +316,7 @@ Active = ΕΝΕΡΓΟ Back = ΠΙΣΩ Cancel = ΑΚΥΡΟ Center = ΚΕΝΤΡΟ -ChangingGPUBackends = Η αλλαγή Συστημάτων Υποστήριξης GPU απαιτεί επανεκκίνηση του PPSSPP. Επανεκκίνηση τώρα? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Επιλογή φακέλου αποθήκευσης PPSSPP Confirm Overwrite = ΑΝΤΙΚΑΤΑΣΤΑΣΗ ΔΕΔΟΜΕΝΩΝ; diff --git a/assets/lang/he_IL.ini b/assets/lang/he_IL.ini index ca454f0dca..ccc51a5220 100644 --- a/assets/lang/he_IL.ini +++ b/assets/lang/he_IL.ini @@ -316,8 +316,7 @@ Active = Active Back = << Cancel = לטב Center = Center -ChangingGPUBackends = Changing GPU backends requires PPSSPP to restart. Restart now? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder Confirm Overwrite = ?ולא םינותנ בתכשל הצור diff --git a/assets/lang/he_IL_invert.ini b/assets/lang/he_IL_invert.ini index b8f55b8db2..05498b9c68 100644 --- a/assets/lang/he_IL_invert.ini +++ b/assets/lang/he_IL_invert.ini @@ -316,8 +316,7 @@ Active = Active Back = << Cancel = לטב Center = Center -ChangingGPUBackends = Changing GPU backends requires PPSSPP to restart. Restart now? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder Confirm Overwrite = ?ולא םינותנ בתכשל הצור diff --git a/assets/lang/hr_HR.ini b/assets/lang/hr_HR.ini index 62d6452ca5..6b89a1f847 100644 --- a/assets/lang/hr_HR.ini +++ b/assets/lang/hr_HR.ini @@ -316,8 +316,7 @@ Active = Aktivno Back = Nazad Cancel = Odustani Center = Center -ChangingGPUBackends = Mijenjanje GPU poslužitelja zahtijeva ponovno pokretanje PPSSPP. Ponovno pokrenuti? -ChangingInflightFrames = Mijenjanje grafičkih komandnih poliranja zahtijeva ponovno pokretanje PPSSPP. Ponovno pokrenuti? +Changing this setting requires PPSSPP to restart. = Promjena ove postavke zahtijeva ponovno pokretanje PPSSPP-a. Channel: = Channel: Choose PPSSPP save folder = Izaberi PPSSPP save mapu Confirm Overwrite = Želite li pisati preko podataka? diff --git a/assets/lang/hu_HU.ini b/assets/lang/hu_HU.ini index ebef7ad57e..3e16370cd6 100644 --- a/assets/lang/hu_HU.ini +++ b/assets/lang/hu_HU.ini @@ -316,8 +316,7 @@ Active = Aktív Back = Vissza Cancel = Mégse Center = Középre -ChangingGPUBackends = GPU backend megváltoztatásához újra kell indítani az alkalmazást. Újraindítod most? -ChangingInflightFrames = Grafikai parancs-bufferelés megváltoztatásához újra kell indítani az alkalmazást. Újraindítod most? +Changing this setting requires PPSSPP to restart. = Grafikai parancs-bufferelés megváltoztatásához újra kell indítani az alkalmazást. Újraindítod most? Channel: = Channel: Choose PPSSPP save folder = PPSSPP mentési mappa kiválasztása Confirm Overwrite = Biztosan felülírod? diff --git a/assets/lang/id_ID.ini b/assets/lang/id_ID.ini index a5f36b0883..79fbc6b176 100644 --- a/assets/lang/id_ID.ini +++ b/assets/lang/id_ID.ini @@ -316,8 +316,7 @@ Active = Aktif Back = Kembali Cancel = Batal Center = Tengah -ChangingGPUBackends = Pergantian penyangga GPU memerlukan pemulaian ulang PPSSPP. Mulai ulang sekarang? -ChangingInflightFrames = Mengubah penyangga perintah grafis membutuhkan PPSSPP untuk memulai ulang. Mulai ulang sekarang? +Changing this setting requires PPSSPP to restart. = Mengubah pengaturan ini memerlukan memulai ulang PPSSPP. Channel: = Saluran: Choose PPSSPP save folder = Pilih berkas simpanan PPSSPP Confirm Overwrite = Apakah anda ingin menimpa data? @@ -374,6 +373,7 @@ Remove = Remove Reset = Atur ulang Resize = Atur ulang ukuran Retry = Coba lagi +Restart = Mengulang kembali Save = Simpan Save completed = Selesai menyimpan. Saving = Menyimpan\nMohon tunggu... diff --git a/assets/lang/it_IT.ini b/assets/lang/it_IT.ini index c028a13f09..aa82a31202 100644 --- a/assets/lang/it_IT.ini +++ b/assets/lang/it_IT.ini @@ -316,8 +316,7 @@ Active = Attiva Back = Indietro Cancel = Annulla Center = Centra -ChangingGPUBackends = Per cambiare il renderer GPU sarà necessario riavviare PPSSPP. Riavviare adesso? -ChangingInflightFrames = La modifica del buffering dei comandi grafici richiede il riavvio di PPSSPP. Riavviare adesso? +Changing this setting requires PPSSPP to restart. = La modifica di questa impostazione richiede il riavvio di PPSSPP. Channel: = Canale: Choose PPSSPP save folder = Scegli la cartella di salvataggio PPSSPP Confirm Overwrite = Vuoi sovrascrivere i dati? @@ -373,6 +372,7 @@ Password = Password Remove = Rimuovi Reset = Reset Resize = Ridimensiona +Restart = Riavvia Retry = Riprova Save = Salva Save completed = Salvataggio Completato. diff --git a/assets/lang/ja_JP.ini b/assets/lang/ja_JP.ini index 68c14ccf05..eddaae8fa8 100644 --- a/assets/lang/ja_JP.ini +++ b/assets/lang/ja_JP.ini @@ -316,8 +316,7 @@ Active = アクティブ Back = 戻る Cancel = キャンセル Center = センタリング -ChangingGPUBackends = GPUバックエンドの変更にはPPSSPPの再起動が必要です。今すぐ再起動しますか? -ChangingInflightFrames = グラフィックスコマンドのバッファリングを変更するにはPPSSPPの再起動が必要です。 再起動しますか? +Changing this setting requires PPSSPP to restart. = この設定を変更するには、PPSSPPを再起動する必要があります。 Channel: = Channel: Choose PPSSPP save folder = PPSSPPのセーブ用フォルダを選択する Confirm Overwrite = データを上書きしますか? @@ -373,6 +372,7 @@ Password = Password Remove = 削除 Reset = リセット Resize = リサイズ +Restart = PPSSPPを再起動します。 Retry = リトライ Save = セーブ Save completed = セーブが完了しました。 diff --git a/assets/lang/jv_ID.ini b/assets/lang/jv_ID.ini index eddd558383..a169fe4c30 100644 --- a/assets/lang/jv_ID.ini +++ b/assets/lang/jv_ID.ini @@ -316,8 +316,7 @@ Active = Active Back = Bali Cancel = Mbatalake Center = Center -ChangingGPUBackends = Ganti backends GPU mbutuhake PPSSPP kanggo miwiti maneh. Wiwiti maneh saiki? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Peleh folder penyimpenan PPSSPP Confirm Overwrite = Njenengan arep Panganggoan data iki? diff --git a/assets/lang/ko_KR.ini b/assets/lang/ko_KR.ini index 7797e343f0..7084768aa3 100644 --- a/assets/lang/ko_KR.ini +++ b/assets/lang/ko_KR.ini @@ -317,8 +317,7 @@ Active = 활성화 Back = 뒤로가기 Cancel = 취소 Center = 중앙 -ChangingGPUBackends = GPU 백엔드를 변경하려면 PPSSPP를 다시 시작해야 합니다. 지금 다시 시작하겠습니까? -ChangingInflightFrames = 그래픽 명령 버퍼링을 변경하려면 PPSSPP를 다시 시작해야 합니다. 지금 다시 시작하겠습니까? +Changing this setting requires PPSSPP to restart. = 이 설정을 변경하려면 PPSSPP를 다시 시작해야 합니다. Channel: = 채널: Choose PPSSPP save folder = PPSSPP 저장 폴더 선택 Confirm Overwrite = 데이터를 덮어쓰겠습니까? @@ -374,6 +373,7 @@ Password = 비밀번호 Remove = 제거 Reset = 재설정 Resize = 크기 조정 +Restart = PPSSPP를 다시 시작합니다. Retry = 재시도 Save = 저장 Save completed = 저장이 완료되었습니다. diff --git a/assets/lang/lo_LA.ini b/assets/lang/lo_LA.ini index d4aaba4c02..d0d36e6a0d 100644 --- a/assets/lang/lo_LA.ini +++ b/assets/lang/lo_LA.ini @@ -316,8 +316,7 @@ Active = Active Back = ຍ້ອນກັບ Cancel = ຍົກເລີກ Center = ທາງກາງ -ChangingGPUBackends = ເມື່ອປ່ຽນແປງຮູບແບບການສະໜັບສະໜູນ GPU \n ຈະຕ້ອງເລີ່ມ PPSSPP ໃໝ່ອີກຄັ້ງ. ເລີ່ມໃໝ່ເລີຍບໍ່? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = ເລືອກໂຟນເດີ້ເຊບ PPSSPP Confirm Overwrite = ຕ້ອງການໃຫ້ຂຽນທັບຂໍ້ມູນເກົ່າຫຼືບໍ່? diff --git a/assets/lang/lt-LT.ini b/assets/lang/lt-LT.ini index 345fb9c9d5..339b5adb6c 100644 --- a/assets/lang/lt-LT.ini +++ b/assets/lang/lt-LT.ini @@ -316,8 +316,7 @@ Active = Active Back = Atgal Cancel = Atšaukti Center = Center -ChangingGPUBackends = Changing GPU backends requires PPSSPP to restart. Restart now? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Pasirinkite PPSSPP išsaugojimo aplanką Confirm Overwrite = Ar norite perrašyti šitus duomenis? diff --git a/assets/lang/ms_MY.ini b/assets/lang/ms_MY.ini index 0b28dedc0e..8f636c4fe4 100644 --- a/assets/lang/ms_MY.ini +++ b/assets/lang/ms_MY.ini @@ -316,8 +316,7 @@ Active = Active Back = Balik Cancel = Batal Center = Center -ChangingGPUBackends = Penukaran enjin GPU memerlukan PPSSPP untuk dimulakan semula. Mula Semula? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder Confirm Overwrite = Anda hendak menulis ganti data ini? diff --git a/assets/lang/nl_NL.ini b/assets/lang/nl_NL.ini index 3f5ccbdee4..d4dc6bb100 100644 --- a/assets/lang/nl_NL.ini +++ b/assets/lang/nl_NL.ini @@ -316,8 +316,7 @@ Active = Active Back = Terug Cancel = Annuleren Center = Centreren -ChangingGPUBackends = Als u de GPU-backend wijzigt moet u PPSSPP opnieuw\nopstarten. Nu opnieuw opstarten? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = PPSSPP-opslagmap selecteren Confirm Overwrite = Wilt u de opgeslagen data overschrijven? diff --git a/assets/lang/no_NO.ini b/assets/lang/no_NO.ini index e75f417875..ef25d71bfb 100644 --- a/assets/lang/no_NO.ini +++ b/assets/lang/no_NO.ini @@ -316,8 +316,7 @@ Active = Active Back = Tilbake Cancel = Cancel Center = Center -ChangingGPUBackends = Changing GPU backends requires PPSSPP to restart. Restart now? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder Confirm Overwrite = Do you want to overwrite the data? diff --git a/assets/lang/pl_PL.ini b/assets/lang/pl_PL.ini index b1373e85f4..4294e824ce 100644 --- a/assets/lang/pl_PL.ini +++ b/assets/lang/pl_PL.ini @@ -316,8 +316,7 @@ Active = Aktywny Back = Powrót Cancel = Anuluj Center = Wyśrodkuj -ChangingGPUBackends = Zmiana sterownika grafiki wymaga restartu PPSSPP. Zrestartować teraz? -ChangingInflightFrames = Zmiana komend buffera grafiki wymaka restartu PPSSPP. Zrestartować teraz? +Changing this setting requires PPSSPP to restart. = Zmiana tego ustawienia wymaga ponownego uruchomienia PPSSPP. Channel: = Kanał: Choose PPSSPP save folder = Wybierz folder do zapisu stanów PPSSPP Confirm Overwrite = Czy chcesz nadpisać te dane? @@ -373,6 +372,7 @@ Password = Hasło Remove = Usuń Reset = Resetuj Resize = Przeskaluj +Restart = Uruchom ponownie Retry = Spróbuj ponownie Save = Zapisz Save completed = Zapisywanie zakończone. diff --git a/assets/lang/pt_BR.ini b/assets/lang/pt_BR.ini index 3fda6879c7..e952b69b39 100644 --- a/assets/lang/pt_BR.ini +++ b/assets/lang/pt_BR.ini @@ -340,8 +340,7 @@ Active = Ativo Back = Voltar Cancel = Cancelar Center = Centro -ChangingGPUBackends = Mudar os backends da GPU requer que o PPSSPP reinicie. Reiniciar agora? -ChangingInflightFrames = Mudar o buffer do comando dos gráficos requer que o PPSSPP reinicie. Reiniciar agora? +Changing this setting requires PPSSPP to restart. = A alteração dessa configuração requer a reinicialização do PPSSPP. Channel: = Canal: Choose PPSSPP save folder = Escolha a pasta dos saves do PPSSPP Confirm Overwrite = Você quer sobrescrever os dados? @@ -397,6 +396,7 @@ Password = Senha Remove = Remover Reset = Resetar Resize = Redimensionar +Restart = Reiniciar Retry = Tentar de novo Save = Salvar Save completed = Salvamento completado. diff --git a/assets/lang/pt_PT.ini b/assets/lang/pt_PT.ini index 9c878f7b5c..726a51d81e 100644 --- a/assets/lang/pt_PT.ini +++ b/assets/lang/pt_PT.ini @@ -340,8 +340,7 @@ Active = Ativo Back = Voltar Cancel = Cancelar Center = Centro -ChangingGPUBackends = Mudar os backends da GPU requer que o PPSSPP reinicie. Reiniciar agora? -ChangingInflightFrames = Mudar o buffer do comando dos gráficos requer que o PPSSPP reinicie. Reiniciar agora? +Changing this setting requires PPSSPP to restart. = A alteração dessa configuração requer a reinicialização do PPSSPP. Channel: = Canal: Choose PPSSPP save folder = Escolha a pasta de save do PPSSPP Confirm Overwrite = Queres sobrescrever os dados? @@ -397,6 +396,7 @@ Password = Password Remove = Remove Reset = Reiniciar Resize = Redimensionar +Restart = Reiniciar Retry = Tentar de novo Save = Salvar Save completed = Salvamento completado. diff --git a/assets/lang/ro_RO.ini b/assets/lang/ro_RO.ini index a819db2a2c..cb68cf6ffe 100644 --- a/assets/lang/ro_RO.ini +++ b/assets/lang/ro_RO.ini @@ -317,8 +317,7 @@ Active = Active Back = Înapoi Cancel = Ignoră Center = Center -ChangingGPUBackends = Schimbarea modului GPU necesită restart. Restart acum? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Alege folder pt. salvări PPSSPP Confirm Overwrite = Vrei să suprascrii datele? diff --git a/assets/lang/ru_RU.ini b/assets/lang/ru_RU.ini index f4e70a1dc6..964c8bdfc2 100644 --- a/assets/lang/ru_RU.ini +++ b/assets/lang/ru_RU.ini @@ -316,8 +316,7 @@ Active = Активно Back = Назад Cancel = Отмена Center = По центру -ChangingGPUBackends = Для изменения бэкенда ГП требуется перезапуск. Перезапустить сейчас? -ChangingInflightFrames = Для изменения буферирования команд графики требуется перезапуск. Перезапустить сейчас? +Changing this setting requires PPSSPP to restart. = Изменение этого параметра требует перезапуска PPSSPP. Channel: = Канал: Choose PPSSPP save folder = Выбрать папку с сохранениями Confirm Overwrite = Вы хотите перезаписать данные? @@ -373,6 +372,7 @@ Password = Password Remove = Удалить Reset = Сбросить Resize = Размер +Restart = Перезапуск Retry = Снова Save = Сохранить Save completed = Сохранение завершено. diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index 430960cb98..8df0de7e7c 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -316,8 +316,7 @@ Active = Aktiv Back = Tillbaka Cancel = Avbryt Center = Centrera -ChangingGPUBackends = PPSSPP måste starta om för att byta GPU-backend. Vill du starta om nu? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Att ändra denna inställning kräver att PPSSPP startas om. Channel: = Kanal: Choose PPSSPP save folder = Välj folder att spara i Confirm Overwrite = Vill du skriva över datan? @@ -374,6 +373,7 @@ Password = Lösenord Remove = Ta bort Reset = Återställ Resize = Ändra storlek +Restart = Starta om Retry = Försök igen Save = Spara Save completed = Sparat. diff --git a/assets/lang/tg_PH.ini b/assets/lang/tg_PH.ini index 6413eac3f3..9f75a41cae 100644 --- a/assets/lang/tg_PH.ini +++ b/assets/lang/tg_PH.ini @@ -316,8 +316,7 @@ Active = Active Back = Bumalik Cancel = Kanselahin Center = Gitna -ChangingGPUBackends = Kapag pinalitan ang GPU backends,\nKailangan mong I-restart ang PPSSPP. Magpatuloy? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Piliin ang PPSSPP save folder Confirm Overwrite = Nais mo bang patungan ang datos? diff --git a/assets/lang/th_TH.ini b/assets/lang/th_TH.ini index aeacdc791e..11dd0aaf59 100644 --- a/assets/lang/th_TH.ini +++ b/assets/lang/th_TH.ini @@ -317,8 +317,7 @@ Active = เปิดการทำงาน Back = ย้อนกลับ Cancel = ยกเลิก Center = กึ่งกลาง -ChangingGPUBackends = เมื่อเปลี่ยนแปลงรูปแบบการสนับสนุนของจีพียู\nกรุณาเริ่ม PPSSPP ใหม่อีกครั้งนึง ต้องการรีสตาร์ทเลยไหม? -ChangingInflightFrames = การเปลี่ยนแปลงค่าควบคุมกราฟิกบัฟเฟอร์จำเป็นต้องเริ่ม PPSSPP\nใหม่อีกครั้งนึง ต้องการรีสตาร์ทเลยไหม? +Changing this setting requires PPSSPP to restart. = การเปลี่ยนการตั้งค่านี้จำเป็นต้องรีสตาร์ท PPSSPP Channel: = แชลนอล: Choose PPSSPP save folder = เลือกโฟลเดอร์สำหรับเก็บข้อมูลเซฟของ PPSSPP Confirm Overwrite = ต้องการให้เขียนทับซ้ำกับข้อมูลเดิมเลยหรือไม่? @@ -374,6 +373,7 @@ Password = Password Remove = ลบออก Reset = รีเซ็ต Resize = ขนาด +Restart = เริ่มต้นใหม่ Retry = ลองใหม่ Save = บันทึก Save completed = บันทึกข้อมูลสำเร็จ diff --git a/assets/lang/tr_TR.ini b/assets/lang/tr_TR.ini index ca1d1334c6..4e125cdc75 100644 --- a/assets/lang/tr_TR.ini +++ b/assets/lang/tr_TR.ini @@ -318,8 +318,7 @@ Active = Etkin Back = Geri Cancel = İptal Center = Merkez -ChangingGPUBackends = GPU arkaucunu değiştirmek için PPSSPP'nin yeniden başlatılması gerek. Şimdi yeniden başlatılsın mı? -ChangingInflightFrames = Grafik komut arabelleğini değiştirmek, PPSSPP'nin yeniden başlatılmasını gerektirir. Şimdi yeniden başlat? +Changing this setting requires PPSSPP to restart. = Bu ayarın değiştirilmesi, PPSSPP'nin yeniden başlatılmasını gerektirir. Channel: = Kanal: Choose PPSSPP save folder = PPSSPP kayıt dosyası seç Confirm Overwrite = Veriyi üstüne yazmak istiyor musun? @@ -375,6 +374,7 @@ Password = Password Remove = Remove Reset = Sıfırla Resize = Yeniden Boyutlandır +Restart = Tekrar başlat Retry = Yeniden Dene Save = Kaydet Save completed = Kaydedildi. diff --git a/assets/lang/uk_UA.ini b/assets/lang/uk_UA.ini index 14303f74c9..71b9084cc4 100644 --- a/assets/lang/uk_UA.ini +++ b/assets/lang/uk_UA.ini @@ -316,8 +316,7 @@ Active = Активно Back = Назад Cancel = Відміна Center = По центру -ChangingGPUBackends = Зміна бекенду GPU вимагає перезавантажити PPSSPP. Перезавантажити зараз? -ChangingInflightFrames = Зміна буферизації графічних команд вимагає перезавантажити PPSSPP. Перезавантажити зараз? +Changing this setting requires PPSSPP to restart. = Зміна буферизації графічних команд вимагає перезавантажити PPSSPP. Перезавантажити зараз? Channel: = Channel: Choose PPSSPP save folder = Виберіть папку для збереження Confirm Overwrite = Ви хочете перезаписати дані? diff --git a/assets/lang/vi_VN.ini b/assets/lang/vi_VN.ini index df20414afd..fb889e29fb 100644 --- a/assets/lang/vi_VN.ini +++ b/assets/lang/vi_VN.ini @@ -316,8 +316,7 @@ Active = Hoạt động Back = Trở lại Cancel = Hủy Center = Trung tâm -ChangingGPUBackends = Đổi thư viện đồ họa cho GPU yêu cầu phải khởi động lại PPSSPP.\nBạn có muốn khởi động lại ngay không? -ChangingInflightFrames = Changing graphics command buffering requires PPSSPP to restart. Restart now? +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Chọn thư mục save của PPSSPP Confirm Overwrite = Bạn có muốn ghi đè lên dữ liệu này không? diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index 827ce44e25..ae8246f2b3 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -316,8 +316,7 @@ Active = 激活 Back = 返回 Cancel = 取消 Center = 中心 -ChangingGPUBackends = 更改引擎模式需要重启PPSSPP。立刻重启吗? -ChangingInflightFrames = 更改缓冲图形命令需要重启PPSSPP。立刻重启吗? +Changing this setting requires PPSSPP to restart. = 更改此设置需要重新启动 PPSSPP。 Channel: = 信道: Choose PPSSPP save folder = 选择PPSSPP存档的文件夹 Confirm Overwrite = 您要覆盖这个存档吗? @@ -373,6 +372,7 @@ Password = Password Remove = 删除 Reset = 重置 Resize = 调整大小 +Restart = 重新开始 Retry = 重试 Save = 保存 Save completed = 保存完成。 diff --git a/assets/lang/zh_TW.ini b/assets/lang/zh_TW.ini index 6067bdb027..011bad0655 100644 --- a/assets/lang/zh_TW.ini +++ b/assets/lang/zh_TW.ini @@ -316,8 +316,7 @@ Active = 啟用 Back = 返回 Cancel = 取消 Center = 中心 -ChangingGPUBackends = 變更 GPU 後端需要重新啟動 PPSSPP,要立即重新啟動嗎? -ChangingInflightFrames = 變更圖形命令緩衝處理需要重新啟動 PPSSPP,要立即重新啟動嗎? +Changing this setting requires PPSSPP to restart. = 更改此設置需要重新啟動 PPSSPP。 Channel: = 頻道: Choose PPSSPP save folder = 選擇 PPSSPP 存檔資料夾 Confirm Overwrite = 要覆寫這個存檔的資料嗎? @@ -373,6 +372,7 @@ Password = 密碼 Remove = 移除 Reset = 重設 Resize = 調整大小 +Restart = 重新開始 Retry = 重試 Save = 儲存 Save completed = 儲存完成 From 386976b12366e1d32f06e31dfcb95033cb3d33f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Jul 2023 22:53:52 +0200 Subject: [PATCH 047/167] Translations: Add missing strings and sections --- assets/lang/ar_AE.ini | 19 +++++++++++++++++++ assets/lang/az_AZ.ini | 19 +++++++++++++++++++ assets/lang/bg_BG.ini | 19 +++++++++++++++++++ assets/lang/ca_ES.ini | 19 +++++++++++++++++++ assets/lang/cz_CZ.ini | 19 +++++++++++++++++++ assets/lang/da_DK.ini | 19 +++++++++++++++++++ assets/lang/de_DE.ini | 19 +++++++++++++++++++ assets/lang/dr_ID.ini | 19 +++++++++++++++++++ assets/lang/en_US.ini | 1 + assets/lang/es_ES.ini | 19 +++++++++++++++++++ assets/lang/es_LA.ini | 2 ++ assets/lang/fa_IR.ini | 19 +++++++++++++++++++ assets/lang/fi_FI.ini | 19 +++++++++++++++++++ assets/lang/fr_FR.ini | 19 +++++++++++++++++++ assets/lang/gl_ES.ini | 19 +++++++++++++++++++ assets/lang/gr_EL.ini | 19 +++++++++++++++++++ assets/lang/he_IL.ini | 19 +++++++++++++++++++ assets/lang/he_IL_invert.ini | 19 +++++++++++++++++++ assets/lang/hr_HR.ini | 19 +++++++++++++++++++ assets/lang/hu_HU.ini | 19 +++++++++++++++++++ assets/lang/id_ID.ini | 18 ++++++++++++++++++ assets/lang/it_IT.ini | 18 ++++++++++++++++++ assets/lang/ja_JP.ini | 18 ++++++++++++++++++ assets/lang/jv_ID.ini | 19 +++++++++++++++++++ assets/lang/ko_KR.ini | 18 ++++++++++++++++++ assets/lang/lo_LA.ini | 19 +++++++++++++++++++ assets/lang/lt-LT.ini | 19 +++++++++++++++++++ assets/lang/ms_MY.ini | 19 +++++++++++++++++++ assets/lang/nl_NL.ini | 19 +++++++++++++++++++ assets/lang/no_NO.ini | 19 +++++++++++++++++++ assets/lang/pl_PL.ini | 18 ++++++++++++++++++ assets/lang/pt_BR.ini | 18 ++++++++++++++++++ assets/lang/pt_PT.ini | 18 ++++++++++++++++++ assets/lang/ro_RO.ini | 19 +++++++++++++++++++ assets/lang/ru_RU.ini | 18 ++++++++++++++++++ assets/lang/sv_SE.ini | 1 + assets/lang/tg_PH.ini | 19 +++++++++++++++++++ assets/lang/th_TH.ini | 18 ++++++++++++++++++ assets/lang/tr_TR.ini | 18 ++++++++++++++++++ assets/lang/uk_UA.ini | 19 +++++++++++++++++++ assets/lang/vi_VN.ini | 19 +++++++++++++++++++ assets/lang/zh_CN.ini | 18 ++++++++++++++++++ assets/lang/zh_TW.ini | 18 ++++++++++++++++++ 43 files changed, 752 insertions(+) diff --git a/assets/lang/ar_AE.ini b/assets/lang/ar_AE.ini index 8abd3c1b00..6b9b5167b0 100644 --- a/assets/lang/ar_AE.ini +++ b/assets/lang/ar_AE.ini @@ -14,6 +14,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -380,6 +381,7 @@ Password = Password Remove = Remove Reset = ‎إستعادة Resize = ‎تغيير الحجم +Restart = Restart Retry = ‎حاول مجدداً Save = ‎حفظ Save completed = ‎الحفظ إكتمل. @@ -865,6 +867,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = ‎الشفرات Continue = ‎إكمال diff --git a/assets/lang/az_AZ.ini b/assets/lang/az_AZ.ini index 3070aee99e..44dbe2fd7f 100644 --- a/assets/lang/az_AZ.ini +++ b/assets/lang/az_AZ.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Reset Resize = Resize +Restart = Restart Retry = Retry Save = Save Save completed = Qeyd edildi @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Cheats Continue = Dəvam diff --git a/assets/lang/bg_BG.ini b/assets/lang/bg_BG.ini index f14afd85b7..864f9fee27 100644 --- a/assets/lang/bg_BG.ini +++ b/assets/lang/bg_BG.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Reset Resize = Преоразмеряване +Restart = Restart Retry = Нов опит Save = Запази Save completed = Съхраняването завършено. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = чийтове Continue = Продължи diff --git a/assets/lang/ca_ES.ini b/assets/lang/ca_ES.ini index 96a7ceab5d..53bdb084a5 100644 --- a/assets/lang/ca_ES.ini +++ b/assets/lang/ca_ES.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Borrar Reset = Reiniciar Resize = Canviar la mida +Restart = Restart Retry = Reintentar Save = Guardar Save completed = Guardat completat @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Trucs Continue = Continuar diff --git a/assets/lang/cz_CZ.ini b/assets/lang/cz_CZ.ini index e874f1d375..9840fcfec2 100644 --- a/assets/lang/cz_CZ.ini +++ b/assets/lang/cz_CZ.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Resetovat Resize = Velikost +Restart = Restart Retry = Zkusit znovu Save = Uložit Save completed = Ukládání dokončeno. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Cheaty Continue = Pokračovat diff --git a/assets/lang/da_DK.ini b/assets/lang/da_DK.ini index b6d2e695fb..45676f32a2 100644 --- a/assets/lang/da_DK.ini +++ b/assets/lang/da_DK.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Nulstil Resize = Størrelse +Restart = Restart Retry = Prøv igen Save = Gem Save completed = Gemt. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Snyd Continue = Fortsæt diff --git a/assets/lang/de_DE.ini b/assets/lang/de_DE.ini index f336e671e0..0f49f1a5fa 100644 --- a/assets/lang/de_DE.ini +++ b/assets/lang/de_DE.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Zurücksetzen Resize = Größe ändern +Restart = Restart Retry = Wiederholen Save = Speichern Save completed = Speichern abgeschlossen @@ -857,6 +859,23 @@ Validating address... = Validiere Adresse... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Cheats Continue = Fortsetzen diff --git a/assets/lang/dr_ID.ini b/assets/lang/dr_ID.ini index 8b62f5cc0b..b2c3fb59f0 100644 --- a/assets/lang/dr_ID.ini +++ b/assets/lang/dr_ID.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Reset Resize = Resize +Restart = Restart Retry = Retry Save = Save Save completed = Mangkami dianna. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Cheats Continue = Patarruh diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index d562bfaf1d..7609d01a2a 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -342,6 +342,7 @@ Back = Back Cancel = Cancel Center = Center Channel: = Channel: +Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Choose PPSSPP save folder = Choose PPSSPP save folder Confirm Overwrite = Do you want to overwrite the data? Confirm Save = Do you want to save this data? diff --git a/assets/lang/es_ES.ini b/assets/lang/es_ES.ini index 8bb52d78d4..023191b97b 100644 --- a/assets/lang/es_ES.ini +++ b/assets/lang/es_ES.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Quitar Reset = Reiniciar Resize = Tamaño +Restart = Restart Retry = Reintentar Save = Guardar Save completed = Los datos del juego han sido almacenados. @@ -858,6 +860,23 @@ Validating address... = Validando dirección... WLAN Channel = Canal WLAN You're in Offline Mode, go to lobby or online hall = Estás en modo offline, ve al lobby o a la sala online +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Trucos Continue = Continuar diff --git a/assets/lang/es_LA.ini b/assets/lang/es_LA.ini index 4c03db9479..cebd122485 100644 --- a/assets/lang/es_LA.ini +++ b/assets/lang/es_LA.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -373,6 +374,7 @@ Password = Password Remove = Remove Reset = Reiniciar Resize = Tamaño +Restart = Restart Retry = Reintentar Save = Guardar Save completed = Los datos del juego han sido almacenados. diff --git a/assets/lang/fa_IR.ini b/assets/lang/fa_IR.ini index 388cb8d7eb..94a6ae9665 100644 --- a/assets/lang/fa_IR.ini +++ b/assets/lang/fa_IR.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = حذف Reset = بازیابی Resize = اندازه گیری +Restart = Restart Retry = تلاش مجدد Save = ‎ذخیره Save completed = ‎.ذخیره تکمیل شد @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = ‎کد تقلب Continue = ‎ادامه بازی diff --git a/assets/lang/fi_FI.ini b/assets/lang/fi_FI.ini index 7c40470cad..949d525c02 100644 --- a/assets/lang/fi_FI.ini +++ b/assets/lang/fi_FI.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Reset Resize = Resize +Restart = Restart Retry = Retry Save = Save Save completed = Tallennus onnistui. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Huijaukset Continue = Jatka diff --git a/assets/lang/fr_FR.ini b/assets/lang/fr_FR.ini index 8bc38f564c..3c538fda50 100644 --- a/assets/lang/fr_FR.ini +++ b/assets/lang/fr_FR.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Remise à 0 Resize = Zoomer +Restart = Restart Retry = Réessayer Save = Sauvegarder Save completed = Sauvegarde terminée. @@ -857,6 +859,23 @@ Validating address... = Validation de l'adresse... WLAN Channel = Canal WLAN You're in Offline Mode, go to lobby or online hall = Vous êtes en mode hors ligne, allez dans le hall en ligne. +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Codes de triche Continue = Continuer diff --git a/assets/lang/gl_ES.ini b/assets/lang/gl_ES.ini index 60cf721d73..1139d09c8a 100644 --- a/assets/lang/gl_ES.ini +++ b/assets/lang/gl_ES.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Reiniciar Resize = Tamaño +Restart = Restart Retry = Reintentar Save = Gardar Save completed = Gardado completado. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Trucos Continue = Continuar diff --git a/assets/lang/gr_EL.ini b/assets/lang/gr_EL.ini index bfc30d241a..46a13c6888 100644 --- a/assets/lang/gr_EL.ini +++ b/assets/lang/gr_EL.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = ΕΠΑΝΑΦΟΡΑ Resize = ΑΛΛΑΓΗ ΜΕΓΕΘΟΥΣ +Restart = Restart Retry = ΕΠΑΝΑΛΗΨΗ Save = ΑΠΟΘΗΚΕΥΣΗ Save completed = ΑΠΟΘΗΚΕΥΣΗ ΟΛΟΚΛΗΡΩΘΗΚΕ. @@ -857,6 +859,23 @@ Validating address... = Επικύρωση διεύθυνσης... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Κωδικοί Continue = Συνέχεια diff --git a/assets/lang/he_IL.ini b/assets/lang/he_IL.ini index ccc51a5220..8a3ff39392 100644 --- a/assets/lang/he_IL.ini +++ b/assets/lang/he_IL.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Reset Resize = Resize +Restart = Restart Retry = Retry Save = Save Save completed = .המלשוה הרימש @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Cheats Continue = המשך diff --git a/assets/lang/he_IL_invert.ini b/assets/lang/he_IL_invert.ini index 05498b9c68..d298194e13 100644 --- a/assets/lang/he_IL_invert.ini +++ b/assets/lang/he_IL_invert.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Reset Resize = Resize +Restart = Restart Retry = Retry Save = Save Save completed = .המלשוה הרימש @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Cheats Continue = ךשמה diff --git a/assets/lang/hr_HR.ini b/assets/lang/hr_HR.ini index 6b89a1f847..6c3a1fba37 100644 --- a/assets/lang/hr_HR.ini +++ b/assets/lang/hr_HR.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Reset Resize = Resize +Restart = Restart Retry = Ponovi Save = Spremi Save completed = Spremanje dovršeno. @@ -857,6 +859,23 @@ Validating address... = Provjeravanje adrese... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = Trenutno si u "Offline",idi u lobby ili online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Šifre Continue = Nastavi diff --git a/assets/lang/hu_HU.ini b/assets/lang/hu_HU.ini index 3e16370cd6..f7803a3893 100644 --- a/assets/lang/hu_HU.ini +++ b/assets/lang/hu_HU.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Visszaállít Resize = Átméretez +Restart = Restart Retry = Újra Save = Mentés Save completed = Mentve. @@ -857,6 +859,23 @@ Validating address... = Cím ellenőrzése... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = Offline üzemmódban vagy, menj a lobbiba vagy az online terembe +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Csalások Continue = Folytatás diff --git a/assets/lang/id_ID.ini b/assets/lang/id_ID.ini index 79fbc6b176..cef54215aa 100644 --- a/assets/lang/id_ID.ini +++ b/assets/lang/id_ID.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -858,6 +859,23 @@ Validating address... = Validasi alamat... WLAN Channel = Saluran WLAN You're in Offline Mode, go to lobby or online hall = Anda dalam mode offline, pergi ke lobi atau aula online +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Pengecoh Continue = Lanjutkan diff --git a/assets/lang/it_IT.ini b/assets/lang/it_IT.ini index aa82a31202..089d7b7425 100644 --- a/assets/lang/it_IT.ini +++ b/assets/lang/it_IT.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -830,6 +831,23 @@ Validating address... = Convalida indirizzo... WLAN Channel = Canale WLAN You're in Offline Mode, go to lobby or online hall = Sei in modalità offline, vai alla lobby online. +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Cheat Continue = Continua diff --git a/assets/lang/ja_JP.ini b/assets/lang/ja_JP.ini index eddaae8fa8..49bb1d9770 100644 --- a/assets/lang/ja_JP.ini +++ b/assets/lang/ja_JP.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -829,6 +830,23 @@ Validating address... = アドレスを検証しています... WLAN Channel = WLANチャネル You're in Offline Mode, go to lobby or online hall = オフラインモードです。ロビーかホールに移動してください +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = チート Continue = 再開する diff --git a/assets/lang/jv_ID.ini b/assets/lang/jv_ID.ini index a169fe4c30..dd994bd569 100644 --- a/assets/lang/jv_ID.ini +++ b/assets/lang/jv_ID.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Ngreset Resize = Ubah Ukuran +Restart = Restart Retry = Jajal Maning Save = Simpen Save completed = Nyimpen rampung. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Mbeling Continue = Terusno diff --git a/assets/lang/ko_KR.ini b/assets/lang/ko_KR.ini index 7084768aa3..423b147c8e 100644 --- a/assets/lang/ko_KR.ini +++ b/assets/lang/ko_KR.ini @@ -7,6 +7,7 @@ Account = 계정 Achievement Unlocked = 성과 잠금 해제 Achievements = 성과 Achievements are disabled = 성과 비활성화 +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = 도전 모드 Challenge Mode (no savestates) = 도전 모드 (저장 상태 없음) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -830,6 +831,23 @@ Validating address... = 주소 검증하는 중... WLAN Channel = 무선랜 채널 You're in Offline Mode, go to lobby or online hall = 오프라인 모드입니다. 로비 또는 온라인 홀으로 이동합니다. +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = 치트 Continue = 계속 diff --git a/assets/lang/lo_LA.ini b/assets/lang/lo_LA.ini index d0d36e6a0d..c4aa049711 100644 --- a/assets/lang/lo_LA.ini +++ b/assets/lang/lo_LA.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = ຕັ້ງຄ່າໃໝ່ Resize = ປັບຂະໜາດ +Restart = Restart Retry = ລອງໃໝ່ອີກຄັ້ງ Save = ບັນທຶກ Save completed = ບັນທຶກຂໍ້ມູນສຳເລັດ @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = ສູດໂກງ Continue = ຫຼິ້ນຕໍ່ diff --git a/assets/lang/lt-LT.ini b/assets/lang/lt-LT.ini index 339b5adb6c..1eca885043 100644 --- a/assets/lang/lt-LT.ini +++ b/assets/lang/lt-LT.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Perkrauti Resize = Perkeisti dydį +Restart = Restart Retry = Pabandyti dar kartą Save = Išsaugoti Save completed = Išsaugojimas baigtas. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Kodai Continue = Tęsti diff --git a/assets/lang/ms_MY.ini b/assets/lang/ms_MY.ini index 8f636c4fe4..da3b015a40 100644 --- a/assets/lang/ms_MY.ini +++ b/assets/lang/ms_MY.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Mula semula Resize = Resize +Restart = Restart Retry = Cuba semula Save = Simpan Save completed = Menyimpan selesai. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Cheat Continue = Teruskan diff --git a/assets/lang/nl_NL.ini b/assets/lang/nl_NL.ini index d4dc6bb100..125a0f9d15 100644 --- a/assets/lang/nl_NL.ini +++ b/assets/lang/nl_NL.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Herstellen Resize = Schalen +Restart = Restart Retry = Opnieuw Save = Opslaan Save completed = Het opslaan is voltooid. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Cheats Continue = Doorgaan diff --git a/assets/lang/no_NO.ini b/assets/lang/no_NO.ini index ef25d71bfb..7a325e365e 100644 --- a/assets/lang/no_NO.ini +++ b/assets/lang/no_NO.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Reset Resize = Resize +Restart = Restart Retry = Retry Save = Save Save completed = Ferdig lagret. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Cheats Continue = Fortsettelse diff --git a/assets/lang/pl_PL.ini b/assets/lang/pl_PL.ini index 4294e824ce..64740920f8 100644 --- a/assets/lang/pl_PL.ini +++ b/assets/lang/pl_PL.ini @@ -6,6 +6,7 @@ Account = Konto Achievement Unlocked = Achievement Unlocked Achievements = Osiągnięcia Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Tryb Wyzwania Challenge Mode (no savestates) = Tryb Wyzwania (bez zapisów stanu) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -862,6 +863,23 @@ Validating address... = Uwierzytelnianie addresu... WLAN Channel = Kanał WLAN You're in Offline Mode, go to lobby or online hall = Jesteś w trybie offline; wejdź do lobby lub pokoju online +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Kody Continue = Kontynuuj diff --git a/assets/lang/pt_BR.ini b/assets/lang/pt_BR.ini index e952b69b39..1f77f97d20 100644 --- a/assets/lang/pt_BR.ini +++ b/assets/lang/pt_BR.ini @@ -30,6 +30,7 @@ Account = Conta Achievement Unlocked = Achievement Unlocked Achievements = Conquistas Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Modo de Desafio Challenge Mode (no savestates) = Modo de Desafio (sem save states) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -853,6 +854,23 @@ Validating address... = Validando endereço... WLAN Channel = Canal do WLAN You're in Offline Mode, go to lobby or online hall = Você está no modo offline, vá pro lobby ou pro salão online +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Trapaças Continue = Continuar diff --git a/assets/lang/pt_PT.ini b/assets/lang/pt_PT.ini index 726a51d81e..41d1bd57fd 100644 --- a/assets/lang/pt_PT.ini +++ b/assets/lang/pt_PT.ini @@ -30,6 +30,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -853,6 +854,23 @@ Validating address... = A validar Endereço... WLAN Channel = Canal do WLAN You're in Offline Mode, go to lobby or online hall = Estás no modo Offline, vá para o lobby ou pro salão Online +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Cheats Continue = Continuar diff --git a/assets/lang/ro_RO.ini b/assets/lang/ro_RO.ini index cb68cf6ffe..5bd6fd09c8 100644 --- a/assets/lang/ro_RO.ini +++ b/assets/lang/ro_RO.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -373,6 +374,7 @@ Password = Password Remove = Remove Reset = Resetare Resize = Modificare marime +Restart = Restart Retry = Reîncearcă Save = Salvează Save completed = Salvare Completă @@ -858,6 +860,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Trișare Continue = Continuă diff --git a/assets/lang/ru_RU.ini b/assets/lang/ru_RU.ini index 964c8bdfc2..8c14acd766 100644 --- a/assets/lang/ru_RU.ini +++ b/assets/lang/ru_RU.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -829,6 +830,23 @@ Validating address... = Проверка адреса... WLAN Channel = Канал WLAN You're in Offline Mode, go to lobby or online hall = Вы в автономном режиме, зайдите в лобби или в комнату в Интернете +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Читы Continue = Продолжить diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index 8df0de7e7c..f5d8b9953a 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -6,6 +6,7 @@ Account = Inloggning Achievement Unlocked = Achievement avklarat! Achievements = Achievements Achievements are disabled = Achievements är avstängda +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Utmanings-läge Challenge Mode (no savestates) = Utmanings-läge (inga sparade state) Contacting RetroAchievements server... = Contacting RetroAchievements server... diff --git a/assets/lang/tg_PH.ini b/assets/lang/tg_PH.ini index 9f75a41cae..a3b2b8678d 100644 --- a/assets/lang/tg_PH.ini +++ b/assets/lang/tg_PH.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Ibalik Resize = Resize +Restart = Restart Retry = Ulitin Save = Save Save completed = Kumpletong na-i save. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Mandaya Continue = Ituloy diff --git a/assets/lang/th_TH.ini b/assets/lang/th_TH.ini index 11dd0aaf59..e63ba19d8e 100644 --- a/assets/lang/th_TH.ini +++ b/assets/lang/th_TH.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -859,6 +860,23 @@ Validating address... = กำลังตรวจสอบที่อยู WLAN Channel = ช่องสัญญาณ WLAN You're in Offline Mode, go to lobby or online hall = คุณอยู่ในโหมดออฟไลน์ ต้องเข้าไปในล็อบบี้หรือออนไลน์ฮอลล์ก่อน +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = สูตรโกง Continue = ดำเนินการต่อ diff --git a/assets/lang/tr_TR.ini b/assets/lang/tr_TR.ini index 4e125cdc75..2aa9d35cd6 100644 --- a/assets/lang/tr_TR.ini +++ b/assets/lang/tr_TR.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -860,6 +861,23 @@ Validating address... = Adres doğrulanıyor... WLAN Channel = WLAN Kanalı You're in Offline Mode, go to lobby or online hall = Çevrimdışı moddasınız, lobiye veya çevrimiçi salona gidin +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Hileler Continue = Devam diff --git a/assets/lang/uk_UA.ini b/assets/lang/uk_UA.ini index 71b9084cc4..674833929e 100644 --- a/assets/lang/uk_UA.ini +++ b/assets/lang/uk_UA.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Скинути Resize = Розмір +Restart = Restart Retry = Повторити Save = Зберегти Save completed = Збереження завершено. @@ -857,6 +859,23 @@ Validating address... = Підтвердження адреси... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = Ви перебуваєте в офлайн-режимі, переходите у лоббі чи онлайн-режим +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = Чит-коди Continue = Продовжити diff --git a/assets/lang/vi_VN.ini b/assets/lang/vi_VN.ini index fb889e29fb..7d10198c86 100644 --- a/assets/lang/vi_VN.ini +++ b/assets/lang/vi_VN.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -372,6 +373,7 @@ Password = Password Remove = Remove Reset = Mặc định Resize = Thay đổi kích thước +Restart = Restart Retry = Thử lại Save = Lưu Save completed = Đã lưu xong. @@ -857,6 +859,23 @@ Validating address... = Validating address... WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = cheat Continue = Tiếp tục diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index ae8246f2b3..da6fa6a85a 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -6,6 +6,7 @@ Account = Account Achievement Unlocked = Achievement Unlocked Achievements = Achievements Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = Challenge Mode Challenge Mode (no savestates) = Challenge Mode (no savestates) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -859,6 +860,23 @@ Validating address... = 正在验证地址... WLAN Channel = WLAN信道 You're in Offline Mode, go to lobby or online hall = 您现在处于离线模式, 请进入房间或在线大厅 +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = 金手指 Continue = 继续游戏 diff --git a/assets/lang/zh_TW.ini b/assets/lang/zh_TW.ini index 011bad0655..c3e5be6690 100644 --- a/assets/lang/zh_TW.ini +++ b/assets/lang/zh_TW.ini @@ -6,6 +6,7 @@ Account = 帳戶 Achievement Unlocked = Achievement Unlocked Achievements = 成就 Achievements are disabled = Achievements are disabled +Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now Challenge Mode = 挑戰模式 Challenge Mode (no savestates) = 挑戰模式 (無存檔) Contacting RetroAchievements server... = Contacting RetroAchievements server... @@ -829,6 +830,23 @@ Validating address... = 正在驗證位址… WLAN Channel = WLAN 頻道 You're in Offline Mode, go to lobby or online hall = 您目前處於離線模式,請前往大廳或線上大廳 +[PSPSettings] +Auto = Auto +Chinese (simplified) = Chinese (simplified) +Chinese (traditional) = Chinese (traditional) +Dutch = Dutch +English = English +French = French +Game language = Game language +German = German +Italian = Italian +Japanese = Japanese +Korean = Korean +Games often don't support all languages = Games often don't support all languages +Portuguese = Portuguese +Russian = Russian +Spanish = Spanish + [Pause] Cheats = 密技 Continue = 繼續 From 0dcc1ae5f0be9d4398101faddf62708cd2b6e16f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Jul 2023 23:02:06 +0200 Subject: [PATCH 048/167] Simplify using lambdas --- UI/GameSettingsScreen.cpp | 65 ++++++++++++++++----------------------- UI/GameSettingsScreen.h | 3 -- 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 41006d8114..6f19b9e951 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1413,45 +1413,18 @@ void GameSettingsScreen::TriggerRestart(const char *why) { System_RestartApp(param); } -void GameSettingsScreen::CallbackRenderingBackend(bool yes) { - // If the user ends up deciding not to restart, set the config back to the current backend - // so it doesn't get switched by accident. - if (yes) { - TriggerRestart("GameSettingsScreen::RenderingBackendYes"); - } else { - g_Config.iGPUBackend = (int)GetGPUBackend(); - } -} - -void GameSettingsScreen::CallbackRenderingDevice(bool yes) { - // If the user ends up deciding not to restart, set the config back to the current backend - // so it doesn't get switched by accident. - if (yes) { - TriggerRestart("GameSettingsScreen::RenderingDeviceYes"); - } else { - std::string *deviceNameSetting = GPUDeviceNameSetting(); - if (deviceNameSetting) - *deviceNameSetting = GetGPUBackendDevice(); - // Needed to redraw the setting. - RecreateViews(); - } -} - -void GameSettingsScreen::CallbackInflightFrames(bool yes) { - if (yes) { - TriggerRestart("GameSettingsScreen::InflightFramesYes"); - } else { - g_Config.iInflightFrames = prevInflightFrames_; - } -} - UI::EventReturn GameSettingsScreen::OnRenderingBackend(UI::EventParams &e) { auto di = GetI18NCategory(I18NCat::DIALOG); // It only makes sense to show the restart prompt if the backend was actually changed. if (g_Config.iGPUBackend != (int)GetGPUBackend()) { - screenManager()->push(new PromptScreen(gamePath_, di->T("Changing this setting requires PPSSPP to restart."), di->T("Restart"), di->T("Cancel"), - std::bind(&GameSettingsScreen::CallbackRenderingBackend, this, std::placeholders::_1))); + screenManager()->push(new PromptScreen(gamePath_, di->T("Changing this setting requires PPSSPP to restart."), di->T("Restart"), di->T("Cancel"), [=](bool yes) { + if (yes) { + TriggerRestart("GameSettingsScreen::RenderingBackendYes"); + } else { + g_Config.iGPUBackend = (int)GetGPUBackend(); + } + })); } return UI::EVENT_DONE; } @@ -1462,8 +1435,19 @@ UI::EventReturn GameSettingsScreen::OnRenderingDevice(UI::EventParams &e) { // It only makes sense to show the restart prompt if the device was actually changed. std::string *deviceNameSetting = GPUDeviceNameSetting(); if (deviceNameSetting && *deviceNameSetting != GetGPUBackendDevice()) { - screenManager()->push(new PromptScreen(gamePath_, di->T("Changing this setting requires PPSSPP to restart."), di->T("Restart"), di->T("Cancel"), - std::bind(&GameSettingsScreen::CallbackRenderingDevice, this, std::placeholders::_1))); + screenManager()->push(new PromptScreen(gamePath_, di->T("Changing this setting requires PPSSPP to restart."), di->T("Restart"), di->T("Cancel"), [=](bool yes) { + // If the user ends up deciding not to restart, set the config back to the current backend + // so it doesn't get switched by accident. + if (yes) { + TriggerRestart("GameSettingsScreen::RenderingDeviceYes"); + } else { + std::string *deviceNameSetting = GPUDeviceNameSetting(); + if (deviceNameSetting) + *deviceNameSetting = GetGPUBackendDevice(); + // Needed to redraw the setting. + RecreateViews(); + } + })); } return UI::EVENT_DONE; } @@ -1471,8 +1455,13 @@ UI::EventReturn GameSettingsScreen::OnRenderingDevice(UI::EventParams &e) { UI::EventReturn GameSettingsScreen::OnInflightFramesChoice(UI::EventParams &e) { auto di = GetI18NCategory(I18NCat::DIALOG); if (g_Config.iInflightFrames != prevInflightFrames_) { - screenManager()->push(new PromptScreen(gamePath_, di->T("Changing this setting requires PPSSPP to restart."), di->T("Restart"), di->T("Cancel"), - std::bind(&GameSettingsScreen::CallbackInflightFrames, this, std::placeholders::_1))); + screenManager()->push(new PromptScreen(gamePath_, di->T("Changing this setting requires PPSSPP to restart."), di->T("Restart"), di->T("Cancel"), [=](bool yes) { + if (yes) { + TriggerRestart("GameSettingsScreen::InflightFramesYes"); + } else { + g_Config.iInflightFrames = prevInflightFrames_; + } + })); } return UI::EVENT_DONE; } diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 1a46358900..3658874397 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -38,9 +38,6 @@ public: protected: void CallbackRestoreDefaults(bool yes); - void CallbackRenderingBackend(bool yes); - void CallbackRenderingDevice(bool yes); - void CallbackInflightFrames(bool yes); void CallbackMemstickFolder(bool yes); void dialogFinished(const Screen *dialog, DialogResult result) override; From 312dcfc1c51fef313afa21f02755852045c70302 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 12:05:19 -0700 Subject: [PATCH 049/167] GPU: Correct UV scale for non-jit. --- GPU/Common/VertexDecoderCommon.cpp | 33 +++++++++++++++--------------- GPU/Common/VertexDecoderCommon.h | 1 + 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index 5015d7d0fc..725229d713 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -340,29 +340,29 @@ void VertexDecoder::Step_TcFloatThrough() const void VertexDecoder::Step_TcU8Prescale() const { float *uv = (float *)(decoded_ + decFmt.uvoff); const u8 *uvdata = (const u8 *)(ptr_ + tcoff); - uv[0] = (float)uvdata[0] * (1.f / 128.f) * gstate_c.uv.uScale + gstate_c.uv.uOff; - uv[1] = (float)uvdata[1] * (1.f / 128.f) * gstate_c.uv.vScale + gstate_c.uv.vOff; + uv[0] = (float)uvdata[0] * (1.f / 128.f) * prescaleUV_->uScale + prescaleUV_->uOff; + uv[1] = (float)uvdata[1] * (1.f / 128.f) * prescaleUV_->vScale + prescaleUV_->vOff; } void VertexDecoder::Step_TcU16Prescale() const { float *uv = (float *)(decoded_ + decFmt.uvoff); const u16_le *uvdata = (const u16_le *)(ptr_ + tcoff); - uv[0] = (float)uvdata[0] * (1.f / 32768.f) * gstate_c.uv.uScale + gstate_c.uv.uOff; - uv[1] = (float)uvdata[1] * (1.f / 32768.f) * gstate_c.uv.vScale + gstate_c.uv.vOff; + uv[0] = (float)uvdata[0] * (1.f / 32768.f) * prescaleUV_->uScale + prescaleUV_->uOff; + uv[1] = (float)uvdata[1] * (1.f / 32768.f) * prescaleUV_->vScale + prescaleUV_->vOff; } void VertexDecoder::Step_TcU16DoublePrescale() const { float *uv = (float *)(decoded_ + decFmt.uvoff); const u16_le *uvdata = (const u16_le *)(ptr_ + tcoff); - uv[0] = (float)uvdata[0] * (1.f / 16384.f) * gstate_c.uv.uScale + gstate_c.uv.uOff; - uv[1] = (float)uvdata[1] * (1.f / 16384.f) * gstate_c.uv.vScale + gstate_c.uv.vOff; + uv[0] = (float)uvdata[0] * (1.f / 16384.f) * prescaleUV_->uScale + prescaleUV_->uOff; + uv[1] = (float)uvdata[1] * (1.f / 16384.f) * prescaleUV_->vScale + prescaleUV_->vOff; } void VertexDecoder::Step_TcFloatPrescale() const { float *uv = (float *)(decoded_ + decFmt.uvoff); const float_le *uvdata = (const float_le *)(ptr_ + tcoff); - uv[0] = uvdata[0] * gstate_c.uv.uScale + gstate_c.uv.uOff; - uv[1] = uvdata[1] * gstate_c.uv.vScale + gstate_c.uv.vOff; + uv[0] = uvdata[0] * prescaleUV_->uScale + prescaleUV_->uOff; + uv[1] = uvdata[1] * prescaleUV_->vScale + prescaleUV_->vOff; } void VertexDecoder::Step_TcU8MorphToFloat() const { @@ -436,8 +436,8 @@ void VertexDecoder::Step_TcU8PrescaleMorph() const { } float *out = (float *)(decoded_ + decFmt.uvoff); - out[0] = uv[0] * gstate_c.uv.uScale + gstate_c.uv.uOff; - out[1] = uv[1] * gstate_c.uv.vScale + gstate_c.uv.vOff; + out[0] = uv[0] * prescaleUV_->uScale + prescaleUV_->uOff; + out[1] = uv[1] * prescaleUV_->vScale + prescaleUV_->vOff; } void VertexDecoder::Step_TcU16PrescaleMorph() const { @@ -451,8 +451,8 @@ void VertexDecoder::Step_TcU16PrescaleMorph() const { } float *out = (float *)(decoded_ + decFmt.uvoff); - out[0] = uv[0] * gstate_c.uv.uScale + gstate_c.uv.uOff; - out[1] = uv[1] * gstate_c.uv.vScale + gstate_c.uv.vOff; + out[0] = uv[0] * prescaleUV_->uScale + prescaleUV_->uOff; + out[1] = uv[1] * prescaleUV_->vScale + prescaleUV_->vOff; } void VertexDecoder::Step_TcU16DoublePrescaleMorph() const { @@ -466,8 +466,8 @@ void VertexDecoder::Step_TcU16DoublePrescaleMorph() const { } float *out = (float *)(decoded_ + decFmt.uvoff); - out[0] = uv[0] * gstate_c.uv.uScale + gstate_c.uv.uOff; - out[1] = uv[1] * gstate_c.uv.vScale + gstate_c.uv.vOff; + out[0] = uv[0] * prescaleUV_->uScale + prescaleUV_->uOff; + out[1] = uv[1] * prescaleUV_->vScale + prescaleUV_->vOff; } void VertexDecoder::Step_TcFloatPrescaleMorph() const { @@ -481,8 +481,8 @@ void VertexDecoder::Step_TcFloatPrescaleMorph() const { } float *out = (float *)(decoded_ + decFmt.uvoff); - out[0] = uv[0] * gstate_c.uv.uScale + gstate_c.uv.uOff; - out[1] = uv[1] * gstate_c.uv.vScale + gstate_c.uv.vOff; + out[0] = uv[0] * prescaleUV_->uScale + prescaleUV_->uOff; + out[1] = uv[1] * prescaleUV_->vScale + prescaleUV_->vOff; } void VertexDecoder::Step_ColorInvalid() const @@ -1303,6 +1303,7 @@ void VertexDecoder::DecodeVerts(u8 *decodedptr, const void *verts, const UVScale } else { ptr_ = startPtr; decoded_ = decodedptr; + prescaleUV_ = uvScaleOffset; // Interpret the decode steps for (; count; count--) { for (int i = 0; i < numSteps_; i++) { diff --git a/GPU/Common/VertexDecoderCommon.h b/GPU/Common/VertexDecoderCommon.h index c276a3ca99..6a65ecad91 100644 --- a/GPU/Common/VertexDecoderCommon.h +++ b/GPU/Common/VertexDecoderCommon.h @@ -436,6 +436,7 @@ public: // Mutable decoder state mutable u8 *decoded_ = nullptr; mutable const u8 *ptr_ = nullptr; + mutable const UVScale *prescaleUV_ = nullptr; JittedVertexDecoder jitted_ = 0; int32_t jittedSize_ = 0; From b6f11d6daea4bdcab123b3a26ea654e11c5be40e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 13:06:28 -0700 Subject: [PATCH 050/167] GPU: Add a little tool to debug vertexjit. Although it's too exacting right now, it still helps. --- GPU/Common/VertexDecoderCommon.cpp | 69 +++++++++++++++++++++++++++++- GPU/Common/VertexDecoderCommon.h | 3 ++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index 5015d7d0fc..a87bd70d86 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -43,6 +43,8 @@ static const u8 nrmsize[4] = { 0, 3, 6, 12 }, nrmalign[4] = { 0, 1, 2, 4 }; static const u8 possize[4] = { 3, 3, 6, 12 }, posalign[4] = { 1, 1, 2, 4 }; static const u8 wtsize[4] = { 0, 1, 2, 4 }, wtalign[4] = { 0, 1, 2, 4 }; +static constexpr bool validateJit = false; + // When software skinning. This array is only used when non-jitted - when jitted, the matrix // is kept in registers. alignas(16) static float skinMatrix[12]; @@ -1297,7 +1299,7 @@ void VertexDecoder::DecodeVerts(u8 *decodedptr, const void *verts, const UVScale return; } - if (jitted_) { + if (jitted_ && !validateJit) { // We've compiled the steps into optimized machine code, so just jump! jitted_(startPtr, decodedptr, count, uvScaleOffset); } else { @@ -1311,6 +1313,71 @@ void VertexDecoder::DecodeVerts(u8 *decodedptr, const void *verts, const UVScale ptr_ += size; decoded_ += stride; } + + if (jitted_ && validateJit) { + CompareToJit(startPtr, decodedptr, indexUpperBound - indexLowerBound + 1, uvScaleOffset); + } + } +} + +void VertexDecoder::CompareToJit(const u8 *startPtr, u8 *decodedptr, int count, const UVScale *uvScaleOffset) const { + std::vector jittedBuffer(decFmt.stride * count); + jitted_(startPtr, &jittedBuffer[0], count, uvScaleOffset); + + VertexReader controlReader(decodedptr, GetDecVtxFmt(), fmt_); + VertexReader jittedReader(&jittedBuffer[0], GetDecVtxFmt(), fmt_); + for (int i = 0; i < count; ++i) { + int off = decFmt.stride * i; + // TODO: May need to add tolerances? + if (memcmp(decodedptr + off, &jittedBuffer[off], decFmt.stride) != 0) { + controlReader.Goto(i); + jittedReader.Goto(i); + + char name[512]{}; + ToString(name); + ERROR_LOG(G3D, "Encountered vertexjit mismatch at %d/%d for %s", i, count, name); + if (morphcount > 1) { + printf("Morph:\n"); + for (int j = 0; j < morphcount; ++j) { + printf(" %f\n", gstate_c.morphWeights[j]); + } + } + if (weighttype) { + printf("Bones:\n"); + for (int j = 0; j < nweights; ++j) { + for (int k = 0; k < 4; ++k) { + if (k == 0) + printf(" *"); + else + printf(" "); + printf(" %f,%f,%f\n", gstate.boneMatrix[j * 12 + k * 3 + 0], gstate.boneMatrix[j * 12 + k * 3 + 1], gstate.boneMatrix[j * 12 + k * 3 + 2]); + } + } + } + printf("Src:\n"); + const u8 *s = startPtr + i * size; + for (int j = 0; j < size; ++j) { + int oneoffset = j % onesize_; + if (oneoffset == weightoff && weighttype) + printf(" W:"); + else if (oneoffset == tcoff && tc) + printf(" T:"); + else if (oneoffset == coloff && col) + printf(" C:"); + else if (oneoffset == nrmoff && nrm) + printf(" N:"); + else if (oneoffset == posoff) + printf(" P:"); + printf("%02x ", s[j]); + if (oneoffset == onesize_ - 1) + printf("\n"); + } + printf("Interpreted vertex:\n"); + PrintDecodedVertex(controlReader); + printf("Jit vertex:\n"); + PrintDecodedVertex(jittedReader); + Crash(); + } } } diff --git a/GPU/Common/VertexDecoderCommon.h b/GPU/Common/VertexDecoderCommon.h index c276a3ca99..1f47cd2724 100644 --- a/GPU/Common/VertexDecoderCommon.h +++ b/GPU/Common/VertexDecoderCommon.h @@ -472,6 +472,9 @@ public: u8 biggest; // in practice, alignment. friend class VertexDecoderJitCache; + +private: + void CompareToJit(const u8 *startPtr, u8 *decodedptr, int count, const UVScale *uvScaleOffset) const; }; From 311c78f26bf026a7c573218b09d7e44469566e1c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 14:11:12 -0700 Subject: [PATCH 051/167] GPU: Make the vertexjit diff smarter. --- GPU/Common/VertexDecoderCommon.cpp | 66 +++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index a87bd70d86..4e2b5f7ce1 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -1320,6 +1320,64 @@ void VertexDecoder::DecodeVerts(u8 *decodedptr, const void *verts, const UVScale } } +static float LargestAbsDiff(Vec4f a, Vec4f b, int n) { + Vec4f delta = a - b; + float largest = 0; + for (int i = 0; i < n; ++ i) { + largest = std::max(largest, fabsf(delta[i])); + } + return largest; +} + +static bool DecodedVertsAreSimilar(const VertexReader &vtx1, const VertexReader &vtx2) { + Vec4f vec1{}, vec2{}; + if (vtx1.hasNormal()) { + vtx1.ReadNrm(vec1.AsArray()); + vtx2.ReadNrm(vec2.AsArray()); + float diff = LargestAbsDiff(vec1, vec2, 3); + if (diff >= 1.0 / 512.0f) { + WARN_LOG(G3D, "Normal diff %f", diff); + return false; + } + } + if (vtx1.hasUV()) { + vtx1.ReadUV(vec1.AsArray()); + vtx2.ReadUV(vec2.AsArray()); + float diff = LargestAbsDiff(vec1, vec2, 2); + if (diff >= 1.0 / 512.0f) { + WARN_LOG(G3D, "UV diff %f", diff); + return false; + } + } + if (vtx1.hasColor0()) { + vtx1.ReadColor0(vec1.AsArray()); + vtx2.ReadColor0(vec2.AsArray()); + float diff = LargestAbsDiff(vec1, vec2, 4); + if (diff >= 1.0 / 255.0f) { + WARN_LOG(G3D, "Color0 diff %f", diff); + return false; + } + } + if (vtx1.hasColor1()) { + vtx1.ReadColor1(vec1.AsArray()); + vtx2.ReadColor1(vec2.AsArray()); + float diff = LargestAbsDiff(vec1, vec2, 4); + if (diff >= 1.0 / 255.0f) { + WARN_LOG(G3D, "Color1 diff %f", diff); + return false; + } + } + vtx1.ReadPos(vec1.AsArray()); + vtx2.ReadPos(vec2.AsArray()); + float diff = LargestAbsDiff(vec1, vec2, 3); + if (diff >= 1.0 / 512.0f) { + WARN_LOG(G3D, "Pos diff %f", diff); + return false; + } + + return true; +} + void VertexDecoder::CompareToJit(const u8 *startPtr, u8 *decodedptr, int count, const UVScale *uvScaleOffset) const { std::vector jittedBuffer(decFmt.stride * count); jitted_(startPtr, &jittedBuffer[0], count, uvScaleOffset); @@ -1328,11 +1386,9 @@ void VertexDecoder::CompareToJit(const u8 *startPtr, u8 *decodedptr, int count, VertexReader jittedReader(&jittedBuffer[0], GetDecVtxFmt(), fmt_); for (int i = 0; i < count; ++i) { int off = decFmt.stride * i; - // TODO: May need to add tolerances? - if (memcmp(decodedptr + off, &jittedBuffer[off], decFmt.stride) != 0) { - controlReader.Goto(i); - jittedReader.Goto(i); - + controlReader.Goto(i); + jittedReader.Goto(i); + if (!DecodedVertsAreSimilar(controlReader, jittedReader)) { char name[512]{}; ToString(name); ERROR_LOG(G3D, "Encountered vertexjit mismatch at %d/%d for %s", i, count, name); From f70030fa5b51e486bf6f7f84da56c7b29a568ce9 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 14:20:30 -0700 Subject: [PATCH 052/167] http: Fix hang on early close. --- Common/Net/HTTPClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index 74d3784ab7..cbdb5686cb 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -371,7 +371,7 @@ int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector & while (true) { int sz = readbuf->TakeLineCRLF(&line); - if (!sz) + if (!sz || sz < 0) break; responseHeaders.push_back(line); } From 1790964ffe7be647c26675e471f48eb527338e18 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 12:13:33 -0700 Subject: [PATCH 053/167] riscv: Fix vertexjit skinning, oops. --- GPU/Common/VertexDecoderRiscV.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/GPU/Common/VertexDecoderRiscV.cpp b/GPU/Common/VertexDecoderRiscV.cpp index cbca1892b8..675e0a6e50 100644 --- a/GPU/Common/VertexDecoderRiscV.cpp +++ b/GPU/Common/VertexDecoderRiscV.cpp @@ -838,20 +838,20 @@ void VertexDecoderJitCache::Jit_PosFloat() { } void VertexDecoderJitCache::Jit_PosS8Skin() { - Jit_AnyS8ToFloat(dec_->nrmoff); - Jit_WriteMatrixMul(dec_->decFmt.nrmoff, true); + Jit_AnyS8ToFloat(dec_->posoff); + Jit_WriteMatrixMul(dec_->decFmt.posoff, true); } void VertexDecoderJitCache::Jit_PosS16Skin() { - Jit_AnyS16ToFloat(dec_->nrmoff); - Jit_WriteMatrixMul(dec_->decFmt.nrmoff, true); + Jit_AnyS16ToFloat(dec_->posoff); + Jit_WriteMatrixMul(dec_->decFmt.posoff, true); } void VertexDecoderJitCache::Jit_PosFloatSkin() { - FL(32, fpSrc[0], srcReg, dec_->nrmoff + 0); - FL(32, fpSrc[1], srcReg, dec_->nrmoff + 4); - FL(32, fpSrc[2], srcReg, dec_->nrmoff + 8); - Jit_WriteMatrixMul(dec_->decFmt.nrmoff, true); + FL(32, fpSrc[0], srcReg, dec_->posoff + 0); + FL(32, fpSrc[1], srcReg, dec_->posoff + 4); + FL(32, fpSrc[2], srcReg, dec_->posoff + 8); + Jit_WriteMatrixMul(dec_->decFmt.posoff, true); } void VertexDecoderJitCache::Jit_PosS8Through() { @@ -904,17 +904,17 @@ void VertexDecoderJitCache::Jit_PosFloatMorph() { void VertexDecoderJitCache::Jit_PosS8MorphSkin() { Jit_AnyS8Morph(dec_->posoff, -1); - Jit_WriteMatrixMul(dec_->decFmt.nrmoff, true); + Jit_WriteMatrixMul(dec_->decFmt.posoff, true); } void VertexDecoderJitCache::Jit_PosS16MorphSkin() { Jit_AnyS16Morph(dec_->posoff, -1); - Jit_WriteMatrixMul(dec_->decFmt.nrmoff, true); + Jit_WriteMatrixMul(dec_->decFmt.posoff, true); } void VertexDecoderJitCache::Jit_PosFloatMorphSkin() { Jit_AnyFloatMorph(dec_->posoff, -1); - Jit_WriteMatrixMul(dec_->decFmt.nrmoff, true); + Jit_WriteMatrixMul(dec_->decFmt.posoff, true); } void VertexDecoderJitCache::Jit_Color8888() { From f1c90a601441de92825fed378e46c24150220133 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 14:18:38 -0700 Subject: [PATCH 054/167] riscv: Fix skinning decode, morph and not. Was transposed and using the wrong matrix when morphing. --- GPU/Common/VertexDecoderRiscV.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/GPU/Common/VertexDecoderRiscV.cpp b/GPU/Common/VertexDecoderRiscV.cpp index 675e0a6e50..d20260b0ef 100644 --- a/GPU/Common/VertexDecoderRiscV.cpp +++ b/GPU/Common/VertexDecoderRiscV.cpp @@ -1389,21 +1389,28 @@ void VertexDecoderJitCache::Jit_AnyFloatMorph(int srcoff, int dstoff) { void VertexDecoderJitCache::Jit_WriteMatrixMul(int dstoff, bool pos) { const RiscVReg fpDst[3] = { fpScratchReg1, fpScratchReg2, fpScratchReg3 }; + // When using morph + skin, we don't keep skinMatrix in a reg. + RiscVReg skinMatrixReg = morphBaseReg; + if (dec_->morphcount > 1) { + LI(scratchReg, &skinMatrix[0]); + skinMatrixReg = scratchReg; + } + // First, take care of the 3x3 portion of the matrix. for (int y = 0; y < 3; ++y) { for (int x = 0; x < 3; ++x) { - FL(32, fpScratchReg4, morphBaseReg, (y * 3 + x) * 4); + FL(32, fpScratchReg4, skinMatrixReg, (y * 3 + x) * 4); if (y == 0) - FMUL(32, fpDst[x], fpSrc[x], fpScratchReg4); + FMUL(32, fpDst[x], fpSrc[y], fpScratchReg4); else - FMADD(32, fpDst[x], fpSrc[x], fpScratchReg4, fpDst[x]); + FMADD(32, fpDst[x], fpSrc[y], fpScratchReg4, fpDst[x]); } } // For normal, z is 0 so we skip. if (pos) { for (int x = 0; x < 3; ++x) - FL(32, fpSrc[x], morphBaseReg, (9 + x) * 4); + FL(32, fpSrc[x], skinMatrixReg, (9 + x) * 4); for (int x = 0; x < 3; ++x) FADD(32, fpDst[x], fpDst[x], fpSrc[x]); } From 2625570d217744e0d81670143a7c6b27c78d6ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 23 Jul 2023 23:40:53 +0200 Subject: [PATCH 055/167] Fix issue where achievements wouldn't always activate when starting from frontends My previous workaround was flawed - it didn't work at all since we run http callbacks on the main thread, so no progress was made in the background. Now we really delay starting the game until the achievement system is ready, if it's in the process of logging in. --- Core/RetroAchievements.cpp | 10 ++++++---- Core/RetroAchievements.h | 1 + Core/System.cpp | 4 ++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index e3df57556b..c02c6cd444 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -647,12 +647,14 @@ struct FileContext { static BlockDevice *g_blockDevice; +bool IsReadyToStart() { + return !g_isLoggingIn; +} + void SetGame(const Path &path, FileLoader *fileLoader) { - // If we are currently logging in, give it a couple of seconds. This is not ideal, but works. if (g_isLoggingIn) { - for (int i = 0; i < 3000 / 50; i++) { - sleep_ms(50); - } + // IsReadyToStart should have been checked, so we shouldn't be here. + ERROR_LOG(ACHIEVEMENTS, "Still logging in during SetGame - shouldn't happen"); } if (!g_rcClient || !IsLoggedIn()) { diff --git a/Core/RetroAchievements.h b/Core/RetroAchievements.h index 81648c7b8d..12ba7a3e2f 100644 --- a/Core/RetroAchievements.h +++ b/Core/RetroAchievements.h @@ -106,6 +106,7 @@ bool HasAchievementsOrLeaderboards(); bool LoginAsync(const char *username, const char *password); void Logout(); +bool IsReadyToStart(); void SetGame(const Path &path, FileLoader *fileLoader); void ChangeUMD(const Path &path); // for in-game UMD change void UnloadGame(); // Call when leaving a game. diff --git a/Core/System.cpp b/Core/System.cpp index 4fbe1eaf86..6a1354ef82 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -418,6 +418,10 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { return false; } + if (!Achievements::IsReadyToStart()) { + return false; + } + #if defined(_WIN32) && PPSSPP_ARCH(AMD64) NOTICE_LOG(BOOT, "PPSSPP %s Windows 64 bit", PPSSPP_GIT_VERSION); #elif defined(_WIN32) && !PPSSPP_ARCH(AMD64) From 0c99d6d3fa0ae2a6f8144cc5e863ee5646d1c8de Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 18 Jul 2023 23:54:14 -0700 Subject: [PATCH 056/167] riscv: Workaround Zicsr detection issue. --- Common/RiscVCPUDetect.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Common/RiscVCPUDetect.cpp b/Common/RiscVCPUDetect.cpp index 55165116bd..5292637f18 100644 --- a/Common/RiscVCPUDetect.cpp +++ b/Common/RiscVCPUDetect.cpp @@ -221,7 +221,8 @@ void CPUInfo::Detect() RiscV_D = info.features.D; RiscV_C = info.features.C; RiscV_V = info.features.V; - RiscV_Zicsr = info.features.Zicsr; + // Seems to be wrong sometimes, assume we have it if we have F. + RiscV_Zicsr = info.features.Zicsr || info.features.F; truncate_cpy(brand_string, info.uarch); #endif From e0eace505b95190dad11192a1bf4cc9721f64d43 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 18 Jul 2023 23:56:54 -0700 Subject: [PATCH 057/167] riscv: Fix cases of SetRegToImmediate(). Was using the wrong value for AUIPC, causing assertion failures and wrong values. Also needed to handle sign-extend more carefully. --- Common/RiscVEmitter.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Common/RiscVEmitter.cpp b/Common/RiscVEmitter.cpp index 1fa966e1fb..244ecf6e0c 100644 --- a/Common/RiscVEmitter.cpp +++ b/Common/RiscVEmitter.cpp @@ -1194,13 +1194,24 @@ void RiscVEmitter::SetRegToImmediate(RiscVReg rd, uint64_t value, RiscVReg temp) auto useUpper = [&](int64_t v, void (RiscVEmitter::*upperOp)(RiscVReg, s32), bool force = false) { if (SignReduce64(v, 32) == v || force) { - int32_t lower = (int32_t)SignReduce64(svalue, 12); + int32_t lower = (int32_t)SignReduce64(v, 12); int32_t upper = ((v - lower) >> 12) << 12; - _assert_msg_(force || (int64_t)upper + lower == v, "Upper + ADDI immediate math mistake?"); + bool clearUpper = v >= 0 && upper < 0; + if (clearUpper) { + _assert_msg_(BitsSupported() >= 64, "Shouldn't be possible on 32-bit"); + _assert_msg_(force || (((int64_t)upper + lower) & 0xFFFFFFFF) == v, "Upper + ADDI immediate math mistake?"); + // This isn't safe to do using AUIPC. We can't have the high bit set this way. + if (upperOp == &RiscVEmitter::AUIPC) + return false; + } else { + _assert_msg_(force || (int64_t)upper + lower == v, "Upper + ADDI immediate math mistake?"); + } // Should be fused on some processors. (this->*upperOp)(rd, upper); - if (lower != 0) + if (clearUpper) + ADDIW(rd, rd, lower); + else if (lower != 0) ADDI(rd, rd, lower); return true; } From 030a27f15ddbbfc8bc9e1b08a043468db5429fc4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 09:49:16 -0700 Subject: [PATCH 058/167] riscv: Stop considering bitmanip "B". Apparently it's not being called B anymore and B is still reserved. --- Common/CPUDetect.h | 1 - Common/RiscVCPUDetect.cpp | 12 +++++------- Common/RiscVEmitter.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Common/CPUDetect.h b/Common/CPUDetect.h index 1feb20483f..d6ae56fe2e 100644 --- a/Common/CPUDetect.h +++ b/Common/CPUDetect.h @@ -109,7 +109,6 @@ struct CPUInfo { bool RiscV_D; bool RiscV_C; bool RiscV_V; - bool RiscV_B; bool RiscV_Zicsr; bool RiscV_Zba; bool RiscV_Zbb; diff --git a/Common/RiscVCPUDetect.cpp b/Common/RiscVCPUDetect.cpp index 5292637f18..5f18db8cd9 100644 --- a/Common/RiscVCPUDetect.cpp +++ b/Common/RiscVCPUDetect.cpp @@ -194,7 +194,7 @@ void CPUInfo::Detect() truncate_cpy(cpu_string, parser.ISAString().c_str()); - // A number of CPUs support a limited set of B. It's not all U74, so we use SOC for now... + // A number of CPUs support a limited set of bitmanip. It's not all U74, so we use SOC for now... if (parser.FirmwareMatchesCompatible("starfive,jh7110")) { RiscV_Zba = true; RiscV_Zbb = true; @@ -208,7 +208,6 @@ void CPUInfo::Detect() RiscV_D = ExtensionSupported(hwcap, 'D'); RiscV_C = ExtensionSupported(hwcap, 'C'); RiscV_V = ExtensionSupported(hwcap, 'V'); - RiscV_B = ExtensionSupported(hwcap, 'B'); // We assume as in RVA20U64 that F means Zicsr is available. RiscV_Zicsr = RiscV_F; @@ -242,11 +241,10 @@ std::vector CPUInfo::Features() { { RiscV_D, "Double" }, { RiscV_C, "Compressed" }, { RiscV_V, "Vector" }, - { RiscV_B, "Bitmanip" }, - { RiscV_Zba, "Zba" }, - { RiscV_Zbb, "Zbb" }, - { RiscV_Zbc, "Zbc" }, - { RiscV_Zbs, "Zbs" }, + { RiscV_Zba, "Bitmanip Zba" }, + { RiscV_Zbb, "Bitmanip Zbb" }, + { RiscV_Zbc, "Bitmanip Zbc" }, + { RiscV_Zbs, "Bitmanip Zbs" }, { RiscV_Zicsr, "Zicsr" }, { CPU64bit, "64-bit" }, }; diff --git a/Common/RiscVEmitter.cpp b/Common/RiscVEmitter.cpp index 244ecf6e0c..7b201c0105 100644 --- a/Common/RiscVEmitter.cpp +++ b/Common/RiscVEmitter.cpp @@ -62,10 +62,10 @@ static inline bool SupportsVector() { static inline bool SupportsBitmanip(char zbx) { switch (zbx) { - case 'a': return cpu_info.RiscV_B || cpu_info.RiscV_Zba; - case 'b': return cpu_info.RiscV_B || cpu_info.RiscV_Zbb; - case 'c': return cpu_info.RiscV_B || cpu_info.RiscV_Zbc; - case 's': return cpu_info.RiscV_B || cpu_info.RiscV_Zbs; + case 'a': return cpu_info.RiscV_Zba; + case 'b': return cpu_info.RiscV_Zbb; + case 'c': return cpu_info.RiscV_Zbc; + case 's': return cpu_info.RiscV_Zbs; default: return false; } } From 6ce3ebc9c9f470baf1985dfc97de35d77dc213c4 Mon Sep 17 00:00:00 2001 From: GABO1423 <35014183+GABO1423@users.noreply.github.com> Date: Sun, 23 Jul 2023 18:59:32 -0400 Subject: [PATCH 059/167] (Lang) Tweak Spanish Translations --- assets/lang/es_ES.ini | 104 +++++++++++++++++++++--------------------- assets/lang/es_LA.ini | 92 ++++++++++++++++++++++--------------- 2 files changed, 106 insertions(+), 90 deletions(-) diff --git a/assets/lang/es_ES.ini b/assets/lang/es_ES.ini index 023191b97b..480dad9547 100644 --- a/assets/lang/es_ES.ini +++ b/assets/lang/es_ES.ini @@ -1,42 +1,42 @@ [Achievements] -%1: Leaderboard attempt failed = %1: Leaderboard attempt failed -%1: Leaderboard attempt started = %1: Leaderboard attempt started -%d achievements, %d points = %d achievements, %d points -Account = Account -Achievement Unlocked = Achievement Unlocked -Achievements = Achievements -Achievements are disabled = Achievements are disabled -Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now -Challenge Mode = Challenge Mode -Challenge Mode (no savestates) = Challenge Mode (no savestates) -Contacting RetroAchievements server... = Contacting RetroAchievements server... -Customize = Customize -Earned = You have earned %d of %d achievements, and %d of %d points -Encore Mode = Encore Mode -How to use RetroAchievements = How to use RetroAchievements -In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below -Leaderboard score submission = Leaderboard score submission -Leaderboard submission is enabled = Leaderboard submission is enabled +%d achievements, %d points = %d logros, %d puntos +%1: Leaderboard attempt started = %1: intento para el leaderboard iniciado +%1: Leaderboard attempt failed = %1: intento para el leaderboard fallido +Account = Cuenta +Achievement Unlocked = Logro Desbloqueado +Achievements = Logros +Achievements are disabled = Los logros están desactivados +Can't log in to RetroAchievements right now = No se pudo iniciar sesión a RetroAchievements +Challenge Mode = Modo Desafío +Challenge Mode (no savestates) = Modo Desafío (Sin estados) +Contacting RetroAchievements server... = Contactando al servidor de RetroAchievements... +Customize = Customizar +Earned = Has obtenido %d de %d logros y %d de %d puntos +Encore Mode = Modo Encore +How to use RetroAchievements = Como usar RetroAchievements +In Encore mode - listings may be wrong below = En el modo Encore - las listas de abajo pueden estar equivocadas +Leaderboard score submission = Subir puntos al leaderboard +Leaderboard submission is enabled = Se ha activado la subida de puntos al leaderboard Leaderboards = Leaderboards Links = Links -Locked achievements = Locked achievements -Log bad memory accesses = Log bad memory accesses -Mastered %1 = Mastered %1 -Register on www.retroachievements.org = Register on www.retroachievements.org -RetroAchievements are not available for this game = RetroAchievements are not available for this game -RetroAchievements website = RetroAchievements website +Locked achievements = Logros bloqueados +Log bad memory accesses = Registrar malos accesos a la memoría +Mastered %1 = Completaste %1 +Register on www.retroachievements.org = Registrate en www.retroachievements.org +RetroAchievements are not available for this game = RetroAchievements no está disponible para este juego +RetroAchievements website = Página web de RetroAchievements Rich Presence = Rich Presence -Save state loaded without achievement data = Save state loaded without achievement data -Save states not available in Challenge Mode = Save states not available in Challenge Mode -Sound Effects = Sound Effects -Statistics = Statistics -Submitted %1 for %2 = Submitted %1 for %2 -Syncing achievements data... = Syncing achievements data... -Test Mode = Test Mode -This feature is not available in Challenge Mode = This feature is not available in Challenge Mode -This game has no achievements = This game has no achievements -Unlocked achievements = Unlocked achievements -Unofficial achievements = Unofficial achievements +Save states not available in Challenge Mode = Los estados no están disponibles en el modo desafío +Save state loaded without achievement data = Estado guardado sin información de los logros +Sound Effects = Efectos de sonido +Statistics = Estadísticas +Submitted %1 for %2 = Se subió %1 para %2 +Syncing achievements data... = Sincronizando información de logros... +Test Mode = Modo prueba +This feature is not available in Challenge Mode = Esta opción no está disponible en el modo desafío +This game has no achievements = Este juego no tiene logros +Unlocked achievements = Logros desbloqueados +Unofficial achievements = Logros no oficiales [Audio] Alternate speed volume = Alternar velocidad de sonido @@ -317,7 +317,7 @@ Active = Activo Back = Atrás Cancel = Cancelar Center = Centrar -Changing this setting requires PPSSPP to restart. = Cambiar esta configuración requiere reiniciar PPSSPP.\n¿Reiniciar ahora? +Changing this setting requires PPSSPP to restart. = Cambiar esta opción requiere reiniciar PPSSPP.\n¿Reiniciar ahora? Channel: = Canal: Choose PPSSPP save folder = Elige la carpeta de guardado de PPSSPP. Confirm Overwrite = ¿Deseas sobreescribir los datos? @@ -373,7 +373,7 @@ Password = Password Remove = Quitar Reset = Reiniciar Resize = Tamaño -Restart = Restart +Restart = Reiniciar Retry = Reintentar Save = Guardar Save completed = Los datos del juego han sido almacenados. @@ -861,21 +861,21 @@ WLAN Channel = Canal WLAN You're in Offline Mode, go to lobby or online hall = Estás en modo offline, ve al lobby o a la sala online [PSPSettings] -Auto = Auto -Chinese (simplified) = Chinese (simplified) -Chinese (traditional) = Chinese (traditional) -Dutch = Dutch -English = English -French = French -Game language = Game language -German = German -Italian = Italian -Japanese = Japanese -Korean = Korean -Games often don't support all languages = Games often don't support all languages -Portuguese = Portuguese -Russian = Russian -Spanish = Spanish +Auto = Automático +Chinese (simplified) = Chino (Simplificado) +Chinese (traditional) = Chino (Tradicional) +Dutch = Neerlandés +English = Inglés +French = Francés +Game language = Lenguaje del juego +German = Alemán +Italian = Italiano +Japanese = Japones +Korean = Coreano +Games often don't support all languages = Los juegos usualmente no pueden usar todos los lenguajes +Portuguese = Portugués +Russian = Ruso +Spanish = Castellano [Pause] Cheats = Trucos diff --git a/assets/lang/es_LA.ini b/assets/lang/es_LA.ini index cebd122485..cbbea2cc65 100644 --- a/assets/lang/es_LA.ini +++ b/assets/lang/es_LA.ini @@ -1,43 +1,42 @@ [Achievements] -%1: Leaderboard attempt failed = %1: Leaderboard attempt failed -%1: Leaderboard attempt started = %1: Leaderboard attempt started -%d achievements, %d points = %d achievements, %d points -Account = Account -Achievement Unlocked = Achievement Unlocked -Achievements = Achievements -Achievements are disabled = Achievements are disabled -Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now -Challenge Mode = Challenge Mode -Challenge Mode (no savestates) = Challenge Mode (no savestates) -Contacting RetroAchievements server... = Contacting RetroAchievements server... -Customize = Customize -Earned = You have earned %d of %d achievements, and %d of %d points -Encore Mode = Encore Mode -How to use RetroAchievements = How to use RetroAchievements -In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below -Leaderboard score submission = Leaderboard score submission -Leaderboard submission is enabled = Leaderboard submission is enabled +%d achievements, %d points = %d logros, %d puntos +%1: Leaderboard attempt started = %1: intento para el leaderboard iniciado +%1: Leaderboard attempt failed = %1: intento para el leaderboard fallido +Account = Cuenta +Achievement Unlocked = Logro Desbloqueado +Achievements = Logros +Achievements are disabled = Los logros están desactivados +Can't log in to RetroAchievements right now = No se pudo iniciar sesión a RetroAchievements +Challenge Mode = Modo Desafío +Challenge Mode (no savestates) = Modo Desafío (Sin estados) +Contacting RetroAchievements server... = Contactando al servidor de RetroAchievements... +Customize = Customizar +Earned = Has obtenido %d de %d logros y %d de %d puntos +Encore Mode = Modo Encore +How to use RetroAchievements = Como usar RetroAchievements +In Encore mode - listings may be wrong below = En el modo Encore - las listas de abajo pueden estar equivocadas +Leaderboard score submission = Subir puntos al leaderboard +Leaderboard submission is enabled = Se ha activado la subida de puntos al leaderboard Leaderboards = Leaderboards Links = Links -Locked achievements = Locked achievements -Log bad memory accesses = Log bad memory accesses -Mastered %1 = Mastered %1 -Register on www.retroachievements.org = Register on www.retroachievements.org -RetroAchievements are not available for this game = RetroAchievements are not available for this game -RetroAchievements website = RetroAchievements website +Locked achievements = Logros bloqueados +Log bad memory accesses = Registrar malos accesos a la memoría +Mastered %1 = Completaste %1 +Register on www.retroachievements.org = Registrate en www.retroachievements.org +RetroAchievements are not available for this game = RetroAchievements no está disponible para este juego +RetroAchievements website = Página web de RetroAchievements Rich Presence = Rich Presence -Save state loaded without achievement data = Save state loaded without achievement data -Save states not available in Challenge Mode = Save states not available in Challenge Mode -Sound Effects = Sound Effects -Statistics = Statistics -Submitted %1 for %2 = Submitted %1 for %2 -Syncing achievements data... = Syncing achievements data... -Test Mode = Test Mode -This feature is not available in Challenge Mode = This feature is not available in Challenge Mode -This game has no achievements = This game has no achievements -Unlocked achievements = Unlocked achievements -#Unofficial = Unofficial -Unofficial achievements = Unofficial achievements +Save states not available in Challenge Mode = Los estados no están disponibles en el modo desafío +Save state loaded without achievement data = Estado guardado sin información de los logros +Sound Effects = Efectos de sonido +Statistics = Estadísticas +Submitted %1 for %2 = Se subió %1 para %2 +Syncing achievements data... = Sincronizando información de logros... +Test Mode = Modo prueba +This feature is not available in Challenge Mode = Esta opción no está disponible en el modo desafío +This game has no achievements = Este juego no tiene logros +Unlocked achievements = Logros desbloqueados +Unofficial achievements = Logros no oficiales [Audio] Alternate speed volume = Alternar velocidad de volumen @@ -318,7 +317,7 @@ Active = Activo Back = Atrás Cancel = Cancelar Center = Centrar -Changing this setting requires PPSSPP to restart. = Cambiar esta configuración requiere que PPSSPP se reinicie.\n¿Reiniciar ahora? +Changing this setting requires PPSSPP to restart. = Cambiar esta opción requiere que PPSSPP se reinicie.\n¿Reiniciar ahora? Channel: = Canal: Choose PPSSPP save folder = Elige la carpeta de guardado de PPSSPP. Confirm Overwrite = Hay archivos existentes,\n¿Deseas sobreescribirlos? @@ -374,7 +373,7 @@ Password = Password Remove = Remove Reset = Reiniciar Resize = Tamaño -Restart = Restart +Restart = Reiniciar Retry = Reintentar Save = Guardar Save completed = Los datos del juego han sido almacenados. @@ -860,6 +859,23 @@ Validating address... = Validando dirección ... WLAN Channel = Canal WLAN You're in Offline Mode, go to lobby or online hall = Estás en modo fuera de línea, ve al lobby o sala en línea +[PSPSettings] +Auto = Automático +Chinese (simplified) = Chino (Simplificado) +Chinese (traditional) = Chino (Tradicional) +Dutch = Neerlandés +English = Inglés +French = Francés +Game language = Lenguaje del juego +German = Alemán +Italian = Italiano +Japanese = Japones +Korean = Coreano +Games often don't support all languages = Los juegos usualmente no pueden usar todos los lenguajes +Portuguese = Portugués +Russian = Ruso +Spanish = Español + [Pause] Cheats = Trampas Continue = Continuar From 021aa319d56eeee12d65e5a94ba994819fad9eed Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 16:12:26 -0700 Subject: [PATCH 060/167] Linux: Use _DEFAULT_SOURCE. Needed for gettimeofday() etc. Keeping _BSD_SOURCE for now for compatibility. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 452b74f1a8..9d2e98ac9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -416,7 +416,7 @@ if(NOT MSVC) add_definitions(-Wno-psabi) endif() add_definitions(-D_XOPEN_SOURCE=700) - add_definitions(-D_XOPEN_SOURCE_EXTENDED -D__BSD_VISIBLE=1 -D_BSD_SOURCE) + add_definitions(-D_XOPEN_SOURCE_EXTENDED -D__BSD_VISIBLE=1 -D_BSD_SOURCE -D_DEFAULT_SOURCE) add_definitions(-D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64) elseif(ANDROID) add_definitions(-fsigned-char) From 5cbad1982bec3276968ec3c72a3a7a04d863bb2c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 17:54:22 -0700 Subject: [PATCH 061/167] riscv: Correct 565 morph mistake. Observed ni Valkyria Chronicles 3. --- GPU/Common/VertexDecoderRiscV.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/GPU/Common/VertexDecoderRiscV.cpp b/GPU/Common/VertexDecoderRiscV.cpp index d20260b0ef..df7d04e14d 100644 --- a/GPU/Common/VertexDecoderRiscV.cpp +++ b/GPU/Common/VertexDecoderRiscV.cpp @@ -265,15 +265,15 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int if ((morphFlags & (1 << (int)MorphValuesIndex::COLOR_4)) != 0) { LI(scratchReg, 255.0f / 15.0f); - FMV(FMv::W, FMv::X, fpScratchReg1, scratchReg); + FMV(FMv::W, FMv::X, fpExtra[0], scratchReg); } if ((morphFlags & (1 << (int)MorphValuesIndex::COLOR_5)) != 0) { LI(scratchReg, 255.0f / 31.0f); - FMV(FMv::W, FMv::X, fpScratchReg2, scratchReg); + FMV(FMv::W, FMv::X, fpExtra[1], scratchReg); } if ((morphFlags & (1 << (int)MorphValuesIndex::COLOR_6)) != 0) { LI(scratchReg, 255.0f / 63.0f); - FMV(FMv::W, FMv::X, fpScratchReg3, scratchReg); + FMV(FMv::W, FMv::X, fpExtra[2], scratchReg); } // Premultiply the values we need and store them so we can reuse. @@ -287,11 +287,11 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int if ((morphFlags & (1 << (int)MorphValuesIndex::AS_FLOAT)) != 0) FS(32, fpScratchReg1, morphBaseReg, ((int)MorphValuesIndex::AS_FLOAT * 8 + n) * 4); if ((morphFlags & (1 << (int)MorphValuesIndex::COLOR_4)) != 0) - storePremultiply(fpScratchReg1, MorphValuesIndex::COLOR_4, n); + storePremultiply(fpExtra[0], MorphValuesIndex::COLOR_4, n); if ((morphFlags & (1 << (int)MorphValuesIndex::COLOR_5)) != 0) - storePremultiply(fpScratchReg2, MorphValuesIndex::COLOR_5, n); + storePremultiply(fpExtra[1], MorphValuesIndex::COLOR_5, n); if ((morphFlags & (1 << (int)MorphValuesIndex::COLOR_6)) != 0) - storePremultiply(fpScratchReg3, MorphValuesIndex::COLOR_6, n); + storePremultiply(fpExtra[2], MorphValuesIndex::COLOR_6, n); } } else if (dec_->skinInDecode) { LI(morphBaseReg, &skinMatrix[0]); @@ -538,7 +538,7 @@ void VertexDecoderJitCache::Jit_TcU16ThroughToFloat() { LHU(tempReg1, srcReg, dec_->tcoff + 0); LHU(tempReg2, srcReg, dec_->tcoff + 2); - if (cpu_info.RiscV_B) { + if (cpu_info.RiscV_Zbb) { MINU(boundsMinUReg, boundsMinUReg, tempReg1); MAXU(boundsMaxUReg, boundsMaxUReg, tempReg1); MINU(boundsMinVReg, boundsMinVReg, tempReg2); @@ -1110,7 +1110,7 @@ void VertexDecoderJitCache::Jit_Color565Morph() { SRLI(tempReg1, tempReg1, 6); FMUL(32, fpSrc[1], fpSrc[1], fpScratchReg4, Round::TOZERO); - FCVT(FConv::S, FConv::WU, fpSrc[0], tempReg1, Round::TOZERO); + FCVT(FConv::S, FConv::WU, fpSrc[2], tempReg1, Round::TOZERO); FMUL(32, fpSrc[2], fpSrc[2], fpScratchReg3, Round::TOZERO); for (int n = 1; n < dec_->morphcount; n++) { @@ -1172,7 +1172,7 @@ void VertexDecoderJitCache::Jit_Color5551Morph() { } void VertexDecoderJitCache::Jit_WriteMorphColor(int outOff, bool checkAlpha) { - if (cpu_info.RiscV_B) { + if (cpu_info.RiscV_Zbb) { LI(scratchReg, 0xFF); FCVT(FConv::WU, FConv::S, tempReg1, fpSrc[0], Round::TOZERO); MAX(tempReg1, tempReg1, R_ZERO); From b041e712def8be2d6b08c45765c44f0085271167 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 17:55:07 -0700 Subject: [PATCH 062/167] riscv: Fix signed position bug in vertexjit. --- GPU/Common/VertexDecoderRiscV.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/Common/VertexDecoderRiscV.cpp b/GPU/Common/VertexDecoderRiscV.cpp index df7d04e14d..3dc1cf5c97 100644 --- a/GPU/Common/VertexDecoderRiscV.cpp +++ b/GPU/Common/VertexDecoderRiscV.cpp @@ -867,8 +867,8 @@ void VertexDecoderJitCache::Jit_PosS16Through() { LH(tempReg2, srcReg, dec_->posoff + 2); // This one, Z, has to be unsigned. LHU(tempReg3, srcReg, dec_->posoff + 4); - FCVT(FConv::S, FConv::WU, fpSrc[0], tempReg1, Round::TOZERO); - FCVT(FConv::S, FConv::WU, fpSrc[1], tempReg2, Round::TOZERO); + FCVT(FConv::S, FConv::W, fpSrc[0], tempReg1, Round::TOZERO); + FCVT(FConv::S, FConv::W, fpSrc[1], tempReg2, Round::TOZERO); FCVT(FConv::S, FConv::WU, fpSrc[2], tempReg3, Round::TOZERO); FS(32, fpSrc[0], dstReg, dec_->decFmt.posoff + 0); FS(32, fpSrc[1], dstReg, dec_->decFmt.posoff + 4); From 3468423bb4982037e7c0901ad3a21cf824237473 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 16 Jul 2023 18:51:20 -0700 Subject: [PATCH 063/167] Debugger: Handle missing crash/block ptrs better. --- Core/MIPS/IR/IRJit.cpp | 5 ----- Core/MIPS/IR/IRJit.h | 2 -- Core/MIPS/fake/FakeJit.cpp | 4 ---- Core/MIPS/fake/FakeJit.h | 2 -- Core/MemFault.cpp | 18 +++++++++++++----- UI/DevScreens.cpp | 5 ++++- unittest/JitHarness.cpp | 30 +++++++++++------------------- 7 files changed, 28 insertions(+), 38 deletions(-) diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 6a82ec1f7c..ffab92975c 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -262,11 +262,6 @@ void IRJit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) { Crash(); } -bool IRJit::ReplaceJalTo(u32 dest) { - Crash(); - return false; -} - void IRBlockCache::Clear() { for (int i = 0; i < (int)blocks_.size(); ++i) { blocks_[i].Destroy(i); diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h index 973ddf524e..199c1f95e1 100644 --- a/Core/MIPS/IR/IRJit.h +++ b/Core/MIPS/IR/IRJit.h @@ -172,7 +172,6 @@ public: private: bool CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload); - bool ReplaceJalTo(u32 dest); JitOptions jo; @@ -187,4 +186,3 @@ private: }; } // namespace MIPSComp - diff --git a/Core/MIPS/fake/FakeJit.cpp b/Core/MIPS/fake/FakeJit.cpp index f833526a66..3e696814c8 100644 --- a/Core/MIPS/fake/FakeJit.cpp +++ b/Core/MIPS/fake/FakeJit.cpp @@ -153,10 +153,6 @@ void FakeJit::Comp_RunBlock(MIPSOpcode op) ERROR_LOG(JIT, "Comp_RunBlock should never be reached!"); } -bool FakeJit::ReplaceJalTo(u32 dest) { - return true; -} - void FakeJit::Comp_ReplacementFunc(MIPSOpcode op) { } diff --git a/Core/MIPS/fake/FakeJit.h b/Core/MIPS/fake/FakeJit.h index 44cfefec05..7fe42a6981 100644 --- a/Core/MIPS/fake/FakeJit.h +++ b/Core/MIPS/fake/FakeJit.h @@ -162,8 +162,6 @@ private: void MovFromPC(FakeReg r); void MovToPC(FakeReg r); - bool ReplaceJalTo(u32 dest); - void SaveDowncount(); void RestoreDowncount(); diff --git a/Core/MemFault.cpp b/Core/MemFault.cpp index fe413d6a26..95a7088ae3 100644 --- a/Core/MemFault.cpp +++ b/Core/MemFault.cpp @@ -263,10 +263,15 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { #endif Core_ExecException(targetAddr, currentMIPS->pc, ExecExceptionType::JUMP); // Redirect execution to a crash handler that will switch to CoreState::CORE_RUNTIME_ERROR immediately. - context->CTX_PC = (uintptr_t)MIPSComp::jit->GetCrashHandler(); - ERROR_LOG(MEMMAP, "Bad execution access detected, halting: %08x (last known pc %08x, host: %p)", targetAddr, currentMIPS->pc, (void *)hostAddress); - inCrashHandler = false; - return true; + uintptr_t crashHandler = (uintptr_t)MIPSComp::jit->GetCrashHandler(); + if (crashHandler != 0) { + context->CTX_PC = crashHandler; + ERROR_LOG(MEMMAP, "Bad execution access detected, halting: %08x (last known pc %08x, host: %p)", targetAddr, currentMIPS->pc, (void *)hostAddress); + inCrashHandler = false; + return true; + } + + type = MemoryExceptionType::UNKNOWN; } else if (success) { if (info.isMemoryWrite) { type = MemoryExceptionType::WRITE_WORD; @@ -303,8 +308,11 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { g_lastCrashAddress = codePtr; // Redirect execution to a crash handler that will switch to CoreState::CORE_RUNTIME_ERROR immediately. + uintptr_t crashHandler = 0; if (MIPSComp::jit) - context->CTX_PC = (uintptr_t)MIPSComp::jit->GetCrashHandler(); + crashHandler = (uintptr_t)MIPSComp::jit->GetCrashHandler(); + if (crashHandler != 0) + context->CTX_PC = crashHandler; else handled = false; ERROR_LOG(MEMMAP, "Bad memory access detected! %08x (%p) Stopping emulation. Info:\n%s", guestAddress, (void *)hostAddress, infoString.c_str()); diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 6c7e336ad3..18f887de38 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -1003,7 +1003,7 @@ void JitCompareScreen::UpdateDisasm() { snprintf(temp, sizeof(temp), "%i/%i", currentBlock_, blockCacheDebug->GetNumBlocks()); blockName_->SetText(temp); - if (currentBlock_ < 0 || currentBlock_ >= blockCacheDebug->GetNumBlocks()) { + if (currentBlock_ < 0 || !blockCacheDebug || currentBlock_ >= blockCacheDebug->GetNumBlocks()) { leftDisasm_->Add(new TextView(dev->T("No block"))); rightDisasm_->Add(new TextView(dev->T("No block"))); blockStats_->SetText(""); @@ -1067,6 +1067,9 @@ UI::EventReturn JitCompareScreen::OnShowStats(UI::EventParams &e) { } JitBlockCacheDebugInterface *blockCache = MIPSComp::jit->GetBlockCacheDebugInterface(); + if (!blockCache) + return UI::EVENT_DONE; + BlockCacheStats bcStats; blockCache->ComputeStats(bcStats); NOTICE_LOG(JIT, "Num blocks: %i", bcStats.numBlocks); diff --git a/unittest/JitHarness.cpp b/unittest/JitHarness.cpp index 44f0cdf751..750b39ab57 100644 --- a/unittest/JitHarness.cpp +++ b/unittest/JitHarness.cpp @@ -178,26 +178,18 @@ bool TestJit() { jit_speed = ExecCPUTest(); // Disassemble - JitBlockCache *cache = MIPSComp::jit->GetBlockCache(); - JitBlock *block = cache->GetBlock(0); // Should only be one block. -#if PPSSPP_ARCH(ARM) - std::vector lines = DisassembleArm2(block->normalEntry, block->codeSize); -#elif PPSSPP_ARCH(ARM64) - std::vector lines = DisassembleArm64(block->normalEntry, block->codeSize); -#elif PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) - std::vector lines = DisassembleX86(block->normalEntry, block->codeSize); -#elif PPSSPP_ARCH(RISCV64) - std::vector lines = DisassembleRV64(block->normalEntry, block->codeSize); -#else - std::vector lines; -#endif - // Cut off at 25 due to the repetition above. Might need tweaking for large instructions. - const int cutoff = 25; - for (int i = 0; i < std::min((int)lines.size(), cutoff); i++) { - printf("%s\n", lines[i].c_str()); + JitBlockCacheDebugInterface *cache = MIPSComp::jit->GetBlockCacheDebugInterface(); + if (cache) { + JitBlockDebugInfo block = cache->GetBlockDebugInfo(0); // Should only be one block. + std::vector &lines = block.targetDisasm; + // Cut off at 25 due to the repetition above. Might need tweaking for large instructions. + const int cutoff = 25; + for (int i = 0; i < std::min((int)lines.size(), cutoff); i++) { + printf("%s\n", lines[i].c_str()); + } + if (lines.size() > cutoff) + printf("...\n"); } - if (lines.size() > cutoff) - printf("...\n"); printf("Jit was %fx faster than interp.\n\n", jit_speed / interp_speed); } From e271e43ec560e34d7a9476825f246785a12f7a4f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 16 Jul 2023 18:54:59 -0700 Subject: [PATCH 064/167] riscv: Initial staffolding for IR based jit. --- CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 9 ++++ Core/MIPS/IR/IRJit.h | 4 +- Core/MIPS/JitCommon/JitCommon.cpp | 4 ++ Core/MIPS/RiscV/RiscVJit.cpp | 79 +++++++++++++++++++++++++++++++ Core/MIPS/RiscV/RiscVJit.h | 53 +++++++++++++++++++++ 7 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 Core/MIPS/RiscV/RiscVJit.cpp create mode 100644 Core/MIPS/RiscV/RiscVJit.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 452b74f1a8..9ba6ab7773 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1609,6 +1609,8 @@ list(APPEND CoreExtra ) list(APPEND CoreExtra + Core/MIPS/RiscV/RiscVJit.cpp + Core/MIPS/RiscV/RiscVJit.h GPU/Common/VertexDecoderRiscV.cpp ) diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 5a347d4006..ddbaa5a692 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -593,6 +593,7 @@ + @@ -1161,6 +1162,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 49c2a10c7b..d3efd2f156 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -91,6 +91,9 @@ {678fa299-0ff7-4983-982d-2da47b52e238} + + {067e3128-3aaf-4ed1-b19e-bab11606abe7} + @@ -1204,6 +1207,9 @@ Core + + MIPS\RiscV + @@ -1950,6 +1956,9 @@ Core + + MIPS\RiscV + diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h index 199c1f95e1..756a584ddf 100644 --- a/Core/MIPS/IR/IRJit.h +++ b/Core/MIPS/IR/IRJit.h @@ -170,8 +170,8 @@ public: void LinkBlock(u8 *exitPoint, const u8 *checkedEntry) override; void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override; -private: - bool CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload); +protected: + virtual bool CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload); JitOptions jo; diff --git a/Core/MIPS/JitCommon/JitCommon.cpp b/Core/MIPS/JitCommon/JitCommon.cpp index 20a8fb6e74..4156732d5d 100644 --- a/Core/MIPS/JitCommon/JitCommon.cpp +++ b/Core/MIPS/JitCommon/JitCommon.cpp @@ -45,6 +45,8 @@ #include "../x86/Jit.h" #elif PPSSPP_ARCH(MIPS) #include "../MIPS/MipsJit.h" +#elif PPSSPP_ARCH(RISCV64) +#include "../RiscV/RiscVJit.h" #else #include "../fake/FakeJit.h" #endif @@ -108,6 +110,8 @@ namespace MIPSComp { return new MIPSComp::Jit(mipsState); #elif PPSSPP_ARCH(MIPS) return new MIPSComp::MipsJit(mipsState); +#elif PPSSPP_ARCH(RISCV64) + return new MIPSComp::RiscVJit(mipsState); #else return new MIPSComp::FakeJit(mipsState); #endif diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp new file mode 100644 index 0000000000..a4045947d8 --- /dev/null +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -0,0 +1,79 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/MIPS/RiscV/RiscVJit.h" +#include "Common/Profiler/Profiler.h" + +namespace MIPSComp { + +RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState) { + AllocCodeSpace(1024 * 1024 * 16); + + // TODO: gpr, fpr, GenerateFixedCode(jo); +} + +void RiscVJit::RunLoopUntil(u64 globalticks) { + PROFILE_THIS_SCOPE("jit"); + ((void (*)())enterDispatcher_)(); +} + +bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload) { + if (!IRJit::CompileBlock(em_address, instructions, mipsBytes, preload)) + return false; + + // TODO: Compile native. + return true; +} + +bool RiscVJit::DescribeCodePtr(const u8 *ptr, std::string &name) { + // TODO: Describe for debugging / profiling. + return false; +} + +bool RiscVJit::CodeInRange(const u8 *ptr) const { + return IsInSpace(ptr); +} + +bool RiscVJit::IsAtDispatchFetch(const u8 *ptr) const { + // TODO + return false; +} + +const u8 *RiscVJit::GetDispatcher() const { + // TODO + return nullptr; +} + +const u8 *RiscVJit::GetCrashHandler() const { + // TODO: Implement a crash handler + return nullptr; +} + +void RiscVJit::ClearCache() { + IRJit::ClearCache(); + + ClearCodeSpace(jitStartOffset_); + FlushIcacheSection(region + jitStartOffset_, region + region_size - jitStartOffset_); +} + +void RiscVJit::UpdateFCR31() { + IRJit::UpdateFCR31(); + + // TODO: Handle rounding modes. +} + +} // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h new file mode 100644 index 0000000000..b98fa7d183 --- /dev/null +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -0,0 +1,53 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Common/RiscVEmitter.h" +#include "Core/MIPS/IR/IRJit.h" + +namespace MIPSComp { + +class RiscVJit : public RiscVGen::RiscVCodeBlock, public IRJit { +public: + RiscVJit(MIPSState *mipsState); + + void RunLoopUntil(u64 globalticks) override; + + bool DescribeCodePtr(const u8 *ptr, std::string &name) override; + bool CodeInRange(const u8 *ptr) const override; + bool IsAtDispatchFetch(const u8 *ptr) const override; + const u8 *GetDispatcher() const override; + const u8 *GetCrashHandler() const override; + + void ClearCache() override; + void UpdateFCR31() override; + + // TODO: GetBlockCacheDebugInterface, block linking? + +protected: + bool CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload) override; + +private: + void GenerateFixedCode(const JitOptions &jo); + + const u8 *enterDispatcher_ = nullptr; + + int jitStartOffset_ = 0; +}; + +} // namespace MIPSComp From 47b81985bd28805c4520289a2be755f1693cb11e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 17 Jul 2023 23:28:43 -0700 Subject: [PATCH 065/167] riscv: Initial untested dispatcher. The minimum to actually, probably, running code. Pretty slow. --- CMakeLists.txt | 2 + Common/RiscVEmitter.cpp | 10 ++ Common/RiscVEmitter.h | 7 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 + Core/MIPS/ARM64/Arm64Jit.h | 2 + Core/MIPS/RiscV/RiscVAsm.cpp | 281 ++++++++++++++++++++++++++++++++ Core/MIPS/RiscV/RiscVJit.cpp | 181 ++++++++++++++++++-- Core/MIPS/RiscV/RiscVJit.h | 26 +++ Core/MIPS/RiscV/RiscVRegCache.h | 35 ++++ 10 files changed, 541 insertions(+), 11 deletions(-) create mode 100644 Core/MIPS/RiscV/RiscVAsm.cpp create mode 100644 Core/MIPS/RiscV/RiscVRegCache.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ba6ab7773..a2fec3d2b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1609,8 +1609,10 @@ list(APPEND CoreExtra ) list(APPEND CoreExtra + Core/MIPS/RiscV/RiscVAsm.cpp Core/MIPS/RiscV/RiscVJit.cpp Core/MIPS/RiscV/RiscVJit.h + Core/MIPS/RiscV/RiscVRegCache.h GPU/Common/VertexDecoderRiscV.cpp ) diff --git a/Common/RiscVEmitter.cpp b/Common/RiscVEmitter.cpp index 7b201c0105..f557354fff 100644 --- a/Common/RiscVEmitter.cpp +++ b/Common/RiscVEmitter.cpp @@ -1180,6 +1180,16 @@ bool RiscVEmitter::CJInRange(const void *src, const void *dst) const { return BJInRange(src, dst, 12); } +void RiscVEmitter::QuickCallFunction(RiscVReg scratchreg, const u8 *func) { + if (!JInRange(GetCodePointer(), func)) { + // TODO: Might be able to optimize a tiny bit by taking advantage of simm12. + LI(scratchreg, (uintptr_t)func); + JALR(R_RA, scratchreg, 0); + } else { + JAL(R_RA, func); + } +} + void RiscVEmitter::SetRegToImmediate(RiscVReg rd, uint64_t value, RiscVReg temp) { int64_t svalue = (int64_t)value; _assert_msg_(IsGPR(rd) && IsGPR(temp), "SetRegToImmediate only supports GPRs"); diff --git a/Common/RiscVEmitter.h b/Common/RiscVEmitter.h index 16ad0176e5..fd093d2509 100644 --- a/Common/RiscVEmitter.h +++ b/Common/RiscVEmitter.h @@ -213,6 +213,13 @@ public: bool BInRange(const void *func) const; bool JInRange(const void *func) const; + void QuickCallFunction(RiscVReg scratchreg, const u8 *func); + template + void QuickCallFunction(RiscVReg scratchreg, T *func) { + static_assert(std::is_function::value, "QuickCallFunction without function"); + QuickCallFunction(scratchreg, (const u8 *)func); + } + void LUI(RiscVReg rd, s32 simm32); void AUIPC(RiscVReg rd, s32 simm32); diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index ddbaa5a692..044a5c4830 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -593,6 +593,7 @@ + @@ -1163,6 +1164,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index d3efd2f156..5226d44737 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1210,6 +1210,9 @@ MIPS\RiscV + + MIPS\RiscV + @@ -1959,6 +1962,9 @@ MIPS\RiscV + + MIPS\RiscV + diff --git a/Core/MIPS/ARM64/Arm64Jit.h b/Core/MIPS/ARM64/Arm64Jit.h index a9c97a85ad..3f81cd0a75 100644 --- a/Core/MIPS/ARM64/Arm64Jit.h +++ b/Core/MIPS/ARM64/Arm64Jit.h @@ -213,7 +213,9 @@ private: bool ReplaceJalTo(u32 dest); + // Clobbers SCRATCH2. void SaveStaticRegisters(); + // Clobbers SCRATCH2. void LoadStaticRegisters(); void WriteExit(u32 destination, int exit_num); diff --git a/Core/MIPS/RiscV/RiscVAsm.cpp b/Core/MIPS/RiscV/RiscVAsm.cpp new file mode 100644 index 0000000000..58420de286 --- /dev/null +++ b/Core/MIPS/RiscV/RiscVAsm.cpp @@ -0,0 +1,281 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Common/Log.h" +#include "Core/CoreTiming.h" +#include "Core/MemMap.h" +#include "Core/MIPS/RiscV/RiscVJit.h" +#include "Core/MIPS/RiscV/RiscVRegCache.h" +#include "Core/MIPS/JitCommon/JitCommon.h" +#include "Core/MIPS/JitCommon/JitState.h" +#include "Core/System.h" + +namespace MIPSComp { + +using namespace RiscVGen; +using namespace RiscVJitConstants; + +static const bool enableDebug = false; +static const bool enableDisasm = false; + +static void ShowPC(u32 downcount, void *membase, void *jitbase) { + static int count = 0; + if (currentMIPS) { + ERROR_LOG(JIT, "ShowPC : %08x Downcount : %08x %d %p %p", currentMIPS->pc, downcount, count, membase, jitbase); + } else { + ERROR_LOG(JIT, "Universe corrupt?"); + } + //if (count > 2000) + // exit(0); + count++; +} + +void RiscVJit::GenerateFixedCode(const JitOptions &jo) { + BeginWrite(GetMemoryProtectPageSize()); + const u8 *start = AlignCodePage(); + + // TODO + if (jo.useStaticAlloc) { + //saveStaticRegisters = AlignCode16(); + //SW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); + //gpr.EmitSaveStaticRegisters(); + //RET(); + + //loadStaticRegisters = AlignCode16(); + //gpr.EmitLoadStaticRegisters(); + //LW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); + //RET(); + + //start = saveStaticRegisters; + } else { + //saveStaticRegisters = nullptr; + //loadStaticRegisters = nullptr; + } + + // TODO: Do we actually need updateRoundingMode_? Hm. + //applyRoundingMode_ = AlignCode16(); + if (false) { + // Not sure if RISC-V has any flush to zero capability? Leaving it off for now... + LWU(SCRATCH2, CTXREG, offsetof(MIPSState, fcr31)); + + // We can skip if the rounding mode is nearest (0) and flush is not set. + // (as restoreRoundingMode cleared it out anyway) + FixupBranch skip = BEQ(SCRATCH2, R_ZERO); + + // MIPS Rounding Mode: RISC-V + // 0: Round nearest 0 + // 1: Round to zero 1 + // 2: Round up (ceil) 3 + // 3: Round down (floor) 2 + if (cpu_info.RiscV_Zbs) { + BEXTI(SCRATCH1, SCRATCH2, 1); + } else { + ANDI(SCRATCH1, SCRATCH2, 2); + SRLI(SCRATCH1, SCRATCH1, 1); + } + // Swap the lowest bit by the second bit. + XOR(SCRATCH2, SCRATCH2, SCRATCH1); + + FSRM(SCRATCH2); + + SetJumpTarget(skip); + RET(); + } + + //updateRoundingMode_ = AlignCode16(); + if (false) { + LWU(SCRATCH2, CTXREG, offsetof(MIPSState, fcr31)); + + // Set SCRATCH2 to FZ:RM (FZ is bit 24, and RM are lowest 2 bits.) + ANDI(SCRATCH1, SCRATCH2, 1 << 24); + ANDI(SCRATCH2, SCRATCH2, 3); + SRLI(SCRATCH1, SCRATCH1, 22); + OR(SCRATCH2, SCRATCH2, SCRATCH1); + + // Let's update js.currentRoundingFunc with the right convertS0ToSCRATCH1 func. + //LI(SCRATCH1, convertS0ToSCRATCH1); + if (cpu_info.RiscV_Zba) { + SH_ADD(3, SCRATCH1, SCRATCH2, SCRATCH1); + } else { + SLLI(SCRATCH2, SCRATCH2, 3); + ADD(SCRATCH1, SCRATCH1, SCRATCH2); + } + LD(SCRATCH2, SCRATCH1, 0); + //LI(SCRATCH1, &js.currentRoundingFunc); + SW(SCRATCH2, SCRATCH1, 0); + RET(); + } + + enterDispatcher_ = AlignCode16(); + + // Start by saving some regs on the stack. There are 12 GPs and 12 FPs we want. + // Note: we leave R_SP as, well, SP, so it doesn't need to be saved. + _assert_msg_(cpu_info.Mode64bit, "RiscVAsm currently assumes RV64, not RV32 or RV128"); + static constexpr RiscVReg regs_to_save[]{ R_RA, X8, X9, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27 }; + // TODO: Maybe we shouldn't regalloc all of these? Is it worth it? + static constexpr RiscVReg regs_to_save_fp[]{ F8, F9, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27 }; + int saveSize = 8 * (int)(ARRAY_SIZE(regs_to_save) + ARRAY_SIZE(regs_to_save_fp)); + if (saveSize & 0xF) + saveSize += 8; + _assert_msg_((saveSize & 0xF) == 0, "Stack must be kept aligned"); + int saveOffset = 0; + ADDI(R_SP, R_SP, -saveSize); + for (RiscVReg r : regs_to_save) { + SD(r, R_SP, saveOffset); + saveOffset += 8; + } + for (RiscVReg r : regs_to_save_fp) { + FS(64, r, R_SP, saveOffset); + saveOffset += 8; + } + _assert_(saveOffset <= saveSize); + + // Fixed registers, these are always kept when in Jit context. + LI(MEMBASEREG, Memory::base, SCRATCH1); + LI(CTXREG, mips_, SCRATCH1); + LI(JITBASEREG, blockStartAddrs_, SCRATCH1); + + LoadStaticRegisters(); + MovFromPC(SCRATCH1); + outerLoopPCInSCRATCH1_ = GetCodePtr(); + MovToPC(SCRATCH1); + outerLoop_ = GetCodePtr(); + // Advance can change the downcount (or thread), so must save/restore around it. + SaveStaticRegisters(); + RestoreRoundingMode(true); + QuickCallFunction(SCRATCH1, &CoreTiming::Advance); + ApplyRoundingMode(true); + LoadStaticRegisters(); + + dispatcherCheckCoreState_ = GetCodePtr(); + LI(SCRATCH1, &coreState, SCRATCH2); + LW(SCRATCH1, SCRATCH1, 0); + FixupBranch badCoreState = BNE(SCRATCH1, R_ZERO); + + // We just checked coreState, so go to advance if downcount is negative. + BLT(DOWNCOUNTREG, R_ZERO, outerLoop_); + FixupBranch skipToRealDispatch = J(); + + dispatcherPCInSCRATCH1_ = GetCodePtr(); + MovToPC(SCRATCH1); + + dispatcher_ = GetCodePtr(); + FixupBranch bail = BLT(DOWNCOUNTREG, R_ZERO); + SetJumpTarget(skipToRealDispatch); + + dispatcherNoCheck_ = GetCodePtr(); + + // Debug + if (enableDebug) { + MV(X10, DOWNCOUNTREG); + MV(X11, MEMBASEREG); + MV(X12, JITBASEREG); + QuickCallFunction(X13, &ShowPC); + } + + LWU(SCRATCH1, CTXREG, offsetof(MIPSState, pc)); +#ifdef MASKED_PSP_MEMORY + LI(SCRATCH2, 0x3FFFFFFF); + AND(SCRATCH1, SCRATCH1, SCRATCH2); +#endif + ADD(SCRATCH1, SCRATCH1, MEMBASEREG); + dispatcherFetch_ = GetCodePtr(); + LWU(SCRATCH1, SCRATCH1, 0); + SRLI(SCRATCH2, SCRATCH1, 24); + // We're in other words comparing to the top 8 bits of MIPS_EMUHACK_OPCODE by subtracting. + ADDI(SCRATCH2, SCRATCH2, -(MIPS_EMUHACK_OPCODE >> 24)); + FixupBranch skipJump = BNE(SCRATCH2, R_ZERO); + // Use a wall to mask by 0x00FFFFFF and extract the block number. + SLLI(SCRATCH1, SCRATCH1, XLEN - 24); + // But actually, we want * 8, so skip shifting back just a bit. + _assert_msg_(sizeof(blockStartAddrs_[0]) == 8, "RiscVAsm currently assumes pointers are 64-bit"); + SRLI(SCRATCH1, SCRATCH1, XLEN - 24 - 3); + ADD(SCRATCH1, JITBASEREG, SCRATCH1); + // TODO: Consider replacing the block nums after all, just trying to use IR block cache. + LD(SCRATCH1, SCRATCH1, 0); + JR(SCRATCH1); + SetJumpTarget(skipJump); + + // No block found, let's jit. Might be able to optimize reg/frm saving later. + SaveStaticRegisters(); + RestoreRoundingMode(true); + QuickCallFunction(SCRATCH1, &MIPSComp::JitAt); + ApplyRoundingMode(true); + LoadStaticRegisters(); + + // Try again, the block index should be set now. + J(dispatcherNoCheck_); + + SetJumpTarget(bail); + + LI(SCRATCH1, &coreState, SCRATCH2); + LW(SCRATCH1, SCRATCH1, 0); + BEQ(SCRATCH1, R_ZERO, outerLoop_); + + const uint8_t *quitLoop = GetCodePtr(); + SetJumpTarget(badCoreState); + + SaveStaticRegisters(); + RestoreRoundingMode(true); + + _assert_msg_(cpu_info.Mode64bit, "RiscVAsm currently assumes RV64, not RV32 or RV128"); + saveOffset = 0; + for (RiscVReg r : regs_to_save) { + LD(r, R_SP, saveOffset); + saveOffset += 8; + } + for (RiscVReg r : regs_to_save_fp) { + FL(64, r, R_SP, saveOffset); + saveOffset += 8; + } + ADDI(R_SP, R_SP, saveSize); + + RET(); + + // TODO + crashHandler_ = GetCodePtr(); + LI(SCRATCH1, &coreState, SCRATCH2); + LI(SCRATCH2, CORE_RUNTIME_ERROR); + SW(SCRATCH2, SCRATCH1, 0); + J(quitLoop); + + // TODO: Do we need this? + static const Round roundModes[8] = { Round::NEAREST_EVEN, Round::TOZERO, Round::UP, Round::DOWN, Round::NEAREST_EVEN, Round::TOZERO, Round::UP, Round::DOWN }; + for (size_t i = 0; i < ARRAY_SIZE(roundModes); ++i) { + //convertS0ToSCRATCH1[i] = AlignCode16(); + + //FCVT(FConv::W, FConv::S, SCRATCH1, F0, roundModes[i]); + //RET(); + } + + // Leave this at the end, add more stuff above. + if (enableDisasm) { + std::vector lines = DisassembleRV64(start, GetCodePtr() - start); + for (auto s : lines) { + INFO_LOG(JIT, "%s", s.c_str()); + } + } + + // Let's spare the pre-generated code from unprotect-reprotect. + AlignCodePage(); + jitStartOffset_ = (int)(GetCodePtr() - start); + // Don't forget to zap the instruction cache! This must stay at the end of this function. + FlushIcache(); + EndWrite(); +} + +} // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index a4045947d8..00ffdbf393 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -15,15 +15,37 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include "Common/StringUtils.h" +#include "Core/MemMap.h" #include "Core/MIPS/RiscV/RiscVJit.h" +#include "Core/MIPS/RiscV/RiscVRegCache.h" #include "Common/Profiler/Profiler.h" namespace MIPSComp { +using namespace RiscVGen; +using namespace RiscVJitConstants; + +static constexpr int MAX_ALLOWED_JIT_BLOCKS = 262144; + RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState) { + // Automatically disable incompatible options. + if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) { + jo.enablePointerify = false; + } + AllocCodeSpace(1024 * 1024 * 16); - // TODO: gpr, fpr, GenerateFixedCode(jo); + // TODO: Consider replacing block num method form IRJit - this is 2MB. + blockStartAddrs_ = new const u8 *[MAX_ALLOWED_JIT_BLOCKS]; + + // TODO: gpr, fpr + + GenerateFixedCode(jo); +} + +RiscVJit::~RiscVJit() { + delete [] blockStartAddrs_; } void RiscVJit::RunLoopUntil(u64 globalticks) { @@ -32,16 +54,117 @@ void RiscVJit::RunLoopUntil(u64 globalticks) { } bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload) { + // Check that we're not full (we allow less blocks than IR itself.) + if (blocks_.GetNumBlocks() >= MAX_ALLOWED_JIT_BLOCKS - 1) + return false; + if (!IRJit::CompileBlock(em_address, instructions, mipsBytes, preload)) return false; - // TODO: Compile native. + // TODO: Block linking, checked entries and such. + + int block_num = blocks_.GetBlockNumberFromStartAddress(em_address); + _assert_msg_(blockStartAddrs_[block_num] == nullptr, "Block reused before clear"); + blockStartAddrs_[block_num] = GetCodePointer(); + + // TODO: gpr, fpr. + for (const IRInst &inst : instructions) { + CompileIRInst(inst); + + // TODO + if (jo.Disabled(JitDisable::REGALLOC_GPR)) { + //gpr.FlushAll(); + } + if (jo.Disabled(JitDisable::REGALLOC_FPR)) { + //fpr.FlushAll(); + } + + // Safety check, in case we get a bunch of really large jit ops without a lot of branching. + if (GetSpaceLeft() < 0x800) { + return false; + } + } + + // Note: a properly constructed block should never get here. + // TODO: Need to do more than just this? Call a func to set an exception? + LI(SCRATCH2, crashHandler_); + JALR(R_ZERO, SCRATCH2, 0); + + FlushIcache(); + return true; } +static u32 DoIRInst(uint64_t value) { + IRInst inst; + memcpy(&inst, &value, sizeof(inst)); + + return IRInterpret(currentMIPS, &inst, 1); +} + +void RiscVJit::CompileIRInst(IRInst inst) { + // For now, we're gonna do it the slow and ugly way. + uint64_t value; + memcpy(&value, &inst, sizeof(inst)); + + LI(X10, value, SCRATCH2); + SaveStaticRegisters(); + QuickCallFunction(SCRATCH2, &DoIRInst); + LoadStaticRegisters(); + // Result in X10 aka SCRATCH1. + _assert_(X10 == SCRATCH1); + if (BInRange(dispatcherPCInSCRATCH1_)) { + BNE(X10, R_ZERO, dispatcherPCInSCRATCH1_); + } else { + FixupBranch skip = BEQ(X10, R_ZERO); + LI(SCRATCH2, dispatcherPCInSCRATCH1_); + JALR(R_ZERO, SCRATCH2, 0); + SetJumpTarget(skip); + } +} + bool RiscVJit::DescribeCodePtr(const u8 *ptr, std::string &name) { - // TODO: Describe for debugging / profiling. - return false; + // Used in disassembly viewer. + if (ptr == dispatcher_) { + name = "dispatcher"; + } else if (ptr == dispatcherPCInSCRATCH1_) { + name = "dispatcher (PC in SCRATCH1)"; + } else if (ptr == dispatcherNoCheck_) { + name = "dispatcherNoCheck"; + } else if (ptr == enterDispatcher_) { + name = "enterDispatcher"; + } else if (!IsInSpace(ptr)) { + return false; + } else { + uintptr_t uptr = (uintptr_t)ptr; + int block_num = -1; + for (int i = 0; i < MAX_ALLOWED_JIT_BLOCKS; ++i) { + uintptr_t blockptr = (uintptr_t)blockStartAddrs_[i]; + // Out of allocated blocks. + if (uptr == 0) + break; + + if (uptr >= blockptr) + block_num = i; + if (uptr < blockptr) + break; + } + + if (block_num == -1) { + name = "(unknown or deleted block)"; + return true; + } + + const IRBlock *block = blocks_.GetBlock(block_num); + if (block) { + u32 start = 0, size = 0; + block->GetRange(start, size); + name = StringFromFormat("(block %d at %08x)", block_num, start); + return true; + } + return false; + } + return true; } bool RiscVJit::CodeInRange(const u8 *ptr) const { @@ -49,18 +172,15 @@ bool RiscVJit::CodeInRange(const u8 *ptr) const { } bool RiscVJit::IsAtDispatchFetch(const u8 *ptr) const { - // TODO - return false; + return ptr == dispatcherFetch_; } const u8 *RiscVJit::GetDispatcher() const { - // TODO - return nullptr; + return dispatcher_; } const u8 *RiscVJit::GetCrashHandler() const { - // TODO: Implement a crash handler - return nullptr; + return crashHandler_; } void RiscVJit::ClearCache() { @@ -68,12 +188,51 @@ void RiscVJit::ClearCache() { ClearCodeSpace(jitStartOffset_); FlushIcacheSection(region + jitStartOffset_, region + region_size - jitStartOffset_); + + memset(blockStartAddrs_, 0, sizeof(blockStartAddrs_[0]) * MAX_ALLOWED_JIT_BLOCKS); } void RiscVJit::UpdateFCR31() { IRJit::UpdateFCR31(); - // TODO: Handle rounding modes. + // TODO: Handle rounding modes? +} + +void RiscVJit::RestoreRoundingMode(bool force) { + // TODO: Could maybe skip if not hasSetRounding? But that's on IRFrontend... + FSRMI(Round::NEAREST_EVEN); +} + +void RiscVJit::ApplyRoundingMode(bool force) { + // TODO: Also could maybe sometimes skip? + //QuickCallFunction(SCRATCH2, applyRoundingMode_); +} + +void RiscVJit::MovFromPC(RiscVGen::RiscVReg r) { + LWU(r, CTXREG, offsetof(MIPSState, pc)); +} + +void RiscVJit::MovToPC(RiscVGen::RiscVReg r) { + SW(r, CTXREG, offsetof(MIPSState, pc)); +} + +void RiscVJit::SaveStaticRegisters() { + // TODO + //if (jo.useStaticAlloc) { + // QuickCallFunction(SCRATCH2, saveStaticRegisters_); + //} else { + // Inline the single operation + SW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); + //} +} + +void RiscVJit::LoadStaticRegisters() { + // TODO + //if (jo.useStaticAlloc) { + // QuickCallFunction(SCRATCH2, loadStaticRegisters_); + //} else { + LW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); + //} } } // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index b98fa7d183..f51a66006a 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -17,14 +17,19 @@ #pragma once +#include +#include #include "Common/RiscVEmitter.h" #include "Core/MIPS/IR/IRJit.h" +#include "Core/MIPS/JitCommon/JitState.h" +#include "Core/MIPS/JitCommon/JitCommon.h" namespace MIPSComp { class RiscVJit : public RiscVGen::RiscVCodeBlock, public IRJit { public: RiscVJit(MIPSState *mipsState); + ~RiscVJit(); void RunLoopUntil(u64 globalticks) override; @@ -42,12 +47,33 @@ public: protected: bool CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload) override; + void CompileIRInst(IRInst inst); + private: void GenerateFixedCode(const JitOptions &jo); + void RestoreRoundingMode(bool force = false); + void ApplyRoundingMode(bool force = false); + void MovFromPC(RiscVGen::RiscVReg r); + void MovToPC(RiscVGen::RiscVReg r); + + void SaveStaticRegisters(); + void LoadStaticRegisters(); + const u8 *enterDispatcher_ = nullptr; + const u8 *outerLoop_ = nullptr; + const u8 *outerLoopPCInSCRATCH1_ = nullptr; + const u8 *dispatcherCheckCoreState_ = nullptr; + const u8 *dispatcherPCInSCRATCH1_ = nullptr; + const u8 *dispatcher_ = nullptr; + const u8 *dispatcherNoCheck_ = nullptr; + const u8 *dispatcherFetch_ = nullptr; + + const u8 *crashHandler_ = nullptr; + int jitStartOffset_ = 0; + const u8 **blockStartAddrs_ = nullptr; }; } // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVRegCache.h b/Core/MIPS/RiscV/RiscVRegCache.h new file mode 100644 index 0000000000..a52eb95ec9 --- /dev/null +++ b/Core/MIPS/RiscV/RiscVRegCache.h @@ -0,0 +1,35 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Common/RiscVEmitter.h" + +namespace RiscVJitConstants { + +// Note: we don't support 32-bit or 128-bit CPUs currently. +constexpr int XLEN = 64; + +const RiscVGen::RiscVReg DOWNCOUNTREG = RiscVGen::X24; +const RiscVGen::RiscVReg JITBASEREG = RiscVGen::X25; +const RiscVGen::RiscVReg CTXREG = RiscVGen::X26; +const RiscVGen::RiscVReg MEMBASEREG = RiscVGen::X27; +// TODO: Experiment. X7-X13 are compressed regs. X8/X9 are saved so nice for static alloc, though. +const RiscVGen::RiscVReg SCRATCH1 = RiscVGen::X10; +const RiscVGen::RiscVReg SCRATCH2 = RiscVGen::X11; + +} // namespace RiscVJitConstants From b2d3c750f139472995560d2abcde5513df2b3432 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 20 Jul 2023 19:21:00 -0700 Subject: [PATCH 066/167] irjit: Define a specific IRReg type. --- Core/MIPS/IR/IRInst.h | 12 +++++++----- Core/MIPS/IR/IRRegCache.cpp | 16 ++++++++-------- Core/MIPS/IR/IRRegCache.h | 23 ++++++++++++----------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 8c7b67b00a..85549c8d6a 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -283,7 +283,9 @@ enum IRFpCompareMode { LessEqualUnordered, // ule, ngt (less equal, unordered) }; -enum { +typedef u8 IRReg; + +enum : IRReg { IRTEMP_0 = 192, IRTEMP_1, IRTEMP_2, @@ -332,11 +334,11 @@ struct IRMeta { struct IRInst { IROp op; union { - u8 dest; - u8 src3; + IRReg dest; + IRReg src3; }; - u8 src1; - u8 src2; + IRReg src1; + IRReg src2; u32 constant; }; diff --git a/Core/MIPS/IR/IRRegCache.cpp b/Core/MIPS/IR/IRRegCache.cpp index ca54392be1..d731c51a74 100644 --- a/Core/MIPS/IR/IRRegCache.cpp +++ b/Core/MIPS/IR/IRRegCache.cpp @@ -2,7 +2,7 @@ #include "Core/MIPS/IR/IRRegCache.h" #include "Core/MIPS/IR/IRInst.h" -void IRRegCache::Flush(int rd) { +void IRRegCache::Flush(IRReg rd) { if (rd == 0) { return; } @@ -12,7 +12,7 @@ void IRRegCache::Flush(int rd) { } } -void IRRegCache::Discard(int rd) { +void IRRegCache::Discard(IRReg rd) { if (rd == 0) { return; } @@ -31,33 +31,33 @@ void IRRegCache::FlushAll() { } } -void IRRegCache::MapIn(int rd) { +void IRRegCache::MapIn(IRReg rd) { Flush(rd); } -void IRRegCache::MapDirty(int rd) { +void IRRegCache::MapDirty(IRReg rd) { Discard(rd); } -void IRRegCache::MapInIn(int rs, int rt) { +void IRRegCache::MapInIn(IRReg rs, IRReg rt) { Flush(rs); Flush(rt); } -void IRRegCache::MapInInIn(int rd, int rs, int rt) { +void IRRegCache::MapInInIn(IRReg rd, IRReg rs, IRReg rt) { Flush(rd); Flush(rs); Flush(rt); } -void IRRegCache::MapDirtyIn(int rd, int rs) { +void IRRegCache::MapDirtyIn(IRReg rd, IRReg rs) { if (rs != rd) { Discard(rd); } Flush(rs); } -void IRRegCache::MapDirtyInIn(int rd, int rs, int rt) { +void IRRegCache::MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt) { if (rs != rd && rt != rd) { Discard(rd); } diff --git a/Core/MIPS/IR/IRRegCache.h b/Core/MIPS/IR/IRRegCache.h index 9fcdab8b1e..80fad1d6ef 100644 --- a/Core/MIPS/IR/IRRegCache.h +++ b/Core/MIPS/IR/IRRegCache.h @@ -5,6 +5,7 @@ #include "Common/CommonTypes.h" #include "Core/MIPS/MIPS.h" +#include "Core/MIPS/IR/IRInst.h" enum { TOTAL_MAPPABLE_MIPSREGS = 256, @@ -22,26 +23,26 @@ class IRRegCache { public: IRRegCache(IRWriter *ir); - void SetImm(int r, u32 immVal) { + void SetImm(IRReg r, u32 immVal) { reg_[r].isImm = true; reg_[r].immVal = immVal; } - bool IsImm(int r) const { return reg_[r].isImm; } - u32 GetImm(int r) const { return reg_[r].immVal; } + bool IsImm(IRReg r) const { return reg_[r].isImm; } + u32 GetImm(IRReg r) const { return reg_[r].immVal; } void FlushAll(); - void MapDirty(int rd); - void MapIn(int rd); - void MapInIn(int rs, int rt); - void MapInInIn(int rd, int rs, int rt); - void MapDirtyIn(int rd, int rs); - void MapDirtyInIn(int rd, int rs, int rt); + void MapDirty(IRReg rd); + void MapIn(IRReg rd); + void MapInIn(IRReg rs, IRReg rt); + void MapInInIn(IRReg rd, IRReg rs, IRReg rt); + void MapDirtyIn(IRReg rd, IRReg rs); + void MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt); private: - void Flush(int rd); - void Discard(int rd); + void Flush(IRReg rd); + void Discard(IRReg rd); RegIR reg_[TOTAL_MAPPABLE_MIPSREGS]; IRWriter *ir_; }; From 4c1cc2dfdc8b3328271b8fd4223963a67e69a0de Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 20 Jul 2023 19:22:12 -0700 Subject: [PATCH 067/167] riscv: Add a register cache for jit. Not yet actually used. Might be buggy. --- CMakeLists.txt | 3 + Core/Core.vcxproj | 3 + Core/Core.vcxproj.filters | 9 + Core/MIPS/RiscV/RiscVAsm.cpp | 23 +- Core/MIPS/RiscV/RiscVJit.cpp | 41 +- Core/MIPS/RiscV/RiscVJit.h | 8 + Core/MIPS/RiscV/RiscVRegCache.cpp | 935 +++++++++++++++++++++++++++ Core/MIPS/RiscV/RiscVRegCache.h | 135 ++++ Core/MIPS/RiscV/RiscVRegCacheFPU.cpp | 16 + Core/MIPS/RiscV/RiscVRegCacheFPU.h | 18 + 10 files changed, 1164 insertions(+), 27 deletions(-) create mode 100644 Core/MIPS/RiscV/RiscVRegCache.cpp create mode 100644 Core/MIPS/RiscV/RiscVRegCacheFPU.cpp create mode 100644 Core/MIPS/RiscV/RiscVRegCacheFPU.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a2fec3d2b4..7aeeb85377 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1612,7 +1612,10 @@ list(APPEND CoreExtra Core/MIPS/RiscV/RiscVAsm.cpp Core/MIPS/RiscV/RiscVJit.cpp Core/MIPS/RiscV/RiscVJit.h + Core/MIPS/RiscV/RiscVRegCache.cpp Core/MIPS/RiscV/RiscVRegCache.h + Core/MIPS/RiscV/RiscVRegCacheFPU.cpp + Core/MIPS/RiscV/RiscVRegCacheFPU.h GPU/Common/VertexDecoderRiscV.cpp ) diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 044a5c4830..fae61b7fbd 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -595,6 +595,8 @@ + + @@ -1165,6 +1167,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 5226d44737..910a58a806 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1213,6 +1213,12 @@ MIPS\RiscV + + MIPS\RiscV + + + MIPS\RiscV + @@ -1965,6 +1971,9 @@ MIPS\RiscV + + MIPS\RiscV + diff --git a/Core/MIPS/RiscV/RiscVAsm.cpp b/Core/MIPS/RiscV/RiscVAsm.cpp index 58420de286..77ab1238db 100644 --- a/Core/MIPS/RiscV/RiscVAsm.cpp +++ b/Core/MIPS/RiscV/RiscVAsm.cpp @@ -48,22 +48,21 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { BeginWrite(GetMemoryProtectPageSize()); const u8 *start = AlignCodePage(); - // TODO if (jo.useStaticAlloc) { - //saveStaticRegisters = AlignCode16(); - //SW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); - //gpr.EmitSaveStaticRegisters(); - //RET(); + saveStaticRegisters_ = AlignCode16(); + SW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); + gpr.EmitSaveStaticRegisters(); + RET(); - //loadStaticRegisters = AlignCode16(); - //gpr.EmitLoadStaticRegisters(); - //LW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); - //RET(); + loadStaticRegisters_ = AlignCode16(); + gpr.EmitLoadStaticRegisters(); + LW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); + RET(); - //start = saveStaticRegisters; + start = saveStaticRegisters_; } else { - //saveStaticRegisters = nullptr; - //loadStaticRegisters = nullptr; + saveStaticRegisters_ = nullptr; + loadStaticRegisters_ = nullptr; } // TODO: Do we actually need updateRoundingMode_? Hm. diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 00ffdbf393..8f5608ac4e 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -28,7 +28,7 @@ using namespace RiscVJitConstants; static constexpr int MAX_ALLOWED_JIT_BLOCKS = 262144; -RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState) { +RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo) { // Automatically disable incompatible options. if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) { jo.enablePointerify = false; @@ -39,7 +39,8 @@ RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState) { // TODO: Consider replacing block num method form IRJit - this is 2MB. blockStartAddrs_ = new const u8 *[MAX_ALLOWED_JIT_BLOCKS]; - // TODO: gpr, fpr + gpr.Init(this); + // TODO: fpr GenerateFixedCode(jo); } @@ -67,14 +68,16 @@ bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u _assert_msg_(blockStartAddrs_[block_num] == nullptr, "Block reused before clear"); blockStartAddrs_[block_num] = GetCodePointer(); - // TODO: gpr, fpr. + gpr.Start(); + // TODO: fpr. + for (const IRInst &inst : instructions) { CompileIRInst(inst); - // TODO if (jo.Disabled(JitDisable::REGALLOC_GPR)) { - //gpr.FlushAll(); + gpr.FlushAll(); } + // TODO if (jo.Disabled(JitDisable::REGALLOC_FPR)) { //fpr.FlushAll(); } @@ -107,6 +110,7 @@ void RiscVJit::CompileIRInst(IRInst inst) { uint64_t value; memcpy(&value, &inst, sizeof(inst)); + FlushAll(); LI(X10, value, SCRATCH2); SaveStaticRegisters(); QuickCallFunction(SCRATCH2, &DoIRInst); @@ -123,6 +127,11 @@ void RiscVJit::CompileIRInst(IRInst inst) { } } +void RiscVJit::FlushAll() { + gpr.FlushAll(); + // TODO: fpr. +} + bool RiscVJit::DescribeCodePtr(const u8 *ptr, std::string &name) { // Used in disassembly viewer. if (ptr == dispatcher_) { @@ -131,6 +140,10 @@ bool RiscVJit::DescribeCodePtr(const u8 *ptr, std::string &name) { name = "dispatcher (PC in SCRATCH1)"; } else if (ptr == dispatcherNoCheck_) { name = "dispatcherNoCheck"; + } else if (ptr == saveStaticRegisters_) { + name = "saveStaticRegisters"; + } else if (ptr == loadStaticRegisters_) { + name = "loadStaticRegisters"; } else if (ptr == enterDispatcher_) { name = "enterDispatcher"; } else if (!IsInSpace(ptr)) { @@ -217,22 +230,20 @@ void RiscVJit::MovToPC(RiscVGen::RiscVReg r) { } void RiscVJit::SaveStaticRegisters() { - // TODO - //if (jo.useStaticAlloc) { - // QuickCallFunction(SCRATCH2, saveStaticRegisters_); - //} else { + if (jo.useStaticAlloc) { + QuickCallFunction(SCRATCH2, saveStaticRegisters_); + } else { // Inline the single operation SW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); - //} + } } void RiscVJit::LoadStaticRegisters() { - // TODO - //if (jo.useStaticAlloc) { - // QuickCallFunction(SCRATCH2, loadStaticRegisters_); - //} else { + if (jo.useStaticAlloc) { + QuickCallFunction(SCRATCH2, loadStaticRegisters_); + } else { LW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); - //} + } } } // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index f51a66006a..13e4e6cf71 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -23,6 +23,7 @@ #include "Core/MIPS/IR/IRJit.h" #include "Core/MIPS/JitCommon/JitState.h" #include "Core/MIPS/JitCommon/JitCommon.h" +#include "Core/MIPS/RiscV/RiscVRegCache.h" namespace MIPSComp { @@ -60,6 +61,10 @@ private: void SaveStaticRegisters(); void LoadStaticRegisters(); + void FlushAll(); + + RiscVRegCache gpr; + const u8 *enterDispatcher_ = nullptr; const u8 *outerLoop_ = nullptr; @@ -70,6 +75,9 @@ private: const u8 *dispatcherNoCheck_ = nullptr; const u8 *dispatcherFetch_ = nullptr; + const u8 *saveStaticRegisters_ = nullptr; + const u8 *loadStaticRegisters_ = nullptr; + const u8 *crashHandler_ = nullptr; int jitStartOffset_ = 0; diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp new file mode 100644 index 0000000000..20aff331fa --- /dev/null +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -0,0 +1,935 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/MIPS/RiscV/RiscVRegCache.h" +#include "Core/MIPS/JitCommon/JitState.h" +#include "Core/Reporting.h" + +#ifndef offsetof +#include "stddef.h" +#endif + +using namespace RiscVGen; +using namespace RiscVJitConstants; + +RiscVRegCache::RiscVRegCache(MIPSState *mipsState, MIPSComp::JitOptions *jo) + : mips_(mipsState), jo_(jo) { +} + +void RiscVRegCache::Init(RiscVEmitter *emitter) { + emit_ = emitter; +} + +void RiscVRegCache::Start() { + for (int i = 0; i < NUM_RVREG; i++) { + ar[i].mipsReg = IRREG_INVALID; + ar[i].isDirty = false; + ar[i].pointerified = false; + ar[i].tempLocked = false; + } + for (int i = 0; i < NUM_MIPSREG; i++) { + mr[i].loc = MIPSLoc::MEM; + mr[i].reg = INVALID_REG; + mr[i].imm = -1; + mr[i].spillLock = false; + mr[i].isStatic = false; + } + int numStatics; + const StaticAllocation *statics = GetStaticAllocations(numStatics); + for (int i = 0; i < numStatics; i++) { + ar[statics[i].ar].mipsReg = statics[i].mr; + ar[statics[i].ar].pointerified = statics[i].pointerified && jo_->enablePointerify; + mr[statics[i].mr].loc = MIPSLoc::RVREG; + mr[statics[i].mr].reg = statics[i].ar; + mr[statics[i].mr].isStatic = true; + mr[statics[i].mr].spillLock = true; + } + + // Treat R_ZERO a bit specially, but it's basically static alloc too. + ar[R_ZERO].mipsReg = MIPS_REG_ZERO; + mr[MIPS_REG_ZERO].loc = MIPSLoc::RVREG_IMM; + mr[MIPS_REG_ZERO].reg = R_ZERO; + mr[MIPS_REG_ZERO].imm = 0; + mr[MIPS_REG_ZERO].isStatic = true; +} + +const RiscVReg *RiscVRegCache::GetMIPSAllocationOrder(int &count) { + // X8 and X9 are the most ideal for static alloc because they can be used with compression. + // Otherwise we stick to saved regs - might not be necessary. + static const RiscVReg allocationOrder[] = { + X7, X8, X9, X12, X13, X14, X1, X5, X6, X15, X16, X17, X18, X19, X20, X21, X22, X23, X28, X29, X30, X31, + }; + static const RiscVReg allocationOrderStaticAlloc[] = { + X7, X12, X13, X14, X1, X5, X6, X15, X16, X17, X21, X22, X23, X28, X29, X30, X31, + }; + + if (jo_->useStaticAlloc) { + count = ARRAY_SIZE(allocationOrderStaticAlloc); + return allocationOrderStaticAlloc; + } else { + count = ARRAY_SIZE(allocationOrder); + return allocationOrder; + } +} + +const RiscVRegCache::StaticAllocation *RiscVRegCache::GetStaticAllocations(int &count) { + static const StaticAllocation allocs[] = { + { MIPS_REG_SP, X8, true }, + { MIPS_REG_V0, X9 }, + { MIPS_REG_V1, X18 }, + { MIPS_REG_A0, X19 }, + { MIPS_REG_RA, X20 }, + }; + + if (jo_->useStaticAlloc) { + count = ARRAY_SIZE(allocs); + return allocs; + } else { + count = 0; + return nullptr; + } +} + +void RiscVRegCache::EmitLoadStaticRegisters() { + int count; + const StaticAllocation *allocs = GetStaticAllocations(count); + for (int i = 0; i < count; i++) { + int offset = GetMipsRegOffset(allocs[i].mr); + emit_->LWU(allocs[i].ar, CTXREG, offset); + if (allocs[i].pointerified && jo_->enablePointerify) { + emit_->ADD(allocs[i].ar, allocs[i].ar, MEMBASEREG); + } + } +} + +void RiscVRegCache::EmitSaveStaticRegisters() { + int count; + const StaticAllocation *allocs = GetStaticAllocations(count); + // This only needs to run once (by Asm) so checks don't need to be fast. + for (int i = 0; i < count; i++) { + int offset = GetMipsRegOffset(allocs[i].mr); + // TODO: Check dirty? + emit_->SW(allocs[i].ar, CTXREG, offset); + } +} + +void RiscVRegCache::FlushBeforeCall() { + // These registers are not preserved by function calls. + for (int i = 5; i <= 7; ++i) { + FlushRiscVReg(RiscVReg(X0 + i)); + } + for (int i = 10; i <= 17; ++i) { + FlushRiscVReg(RiscVReg(X0 + i)); + } + for (int i = 28; i <= 31; ++i) { + FlushRiscVReg(RiscVReg(X0 + i)); + } +} + +bool RiscVRegCache::IsInRAM(IRRegIndex reg) { + _dbg_assert_(IsValidReg(reg)); + return mr[reg].loc == MIPSLoc::MEM; +} + +bool RiscVRegCache::IsMapped(IRRegIndex mipsReg) { + _dbg_assert_(IsValidReg(mipsReg)); + return mr[mipsReg].loc == MIPSLoc::RVREG || mr[mipsReg].loc == MIPSLoc::RVREG_IMM; +} + +bool RiscVRegCache::IsMappedAsPointer(IRRegIndex mipsReg) { + _dbg_assert_(IsValidReg(mipsReg)); + if (mr[mipsReg].loc == MIPSLoc::RVREG) { + return ar[mr[mipsReg].reg].pointerified; + } else if (mr[mipsReg].loc == MIPSLoc::RVREG_IMM) { + if (ar[mr[mipsReg].reg].pointerified) { + ERROR_LOG(JIT, "Really shouldn't be pointerified here"); + } + } else if (mr[mipsReg].loc == MIPSLoc::RVREG_AS_PTR) { + return true; + } + return false; +} + +void RiscVRegCache::MarkDirty(RiscVReg reg) { + // Can't mark X0 dirty. + _dbg_assert_(reg > X0 && reg <= X31); + ar[reg].isDirty = true; +} + +void RiscVRegCache::SetRegImm(RiscVReg reg, u64 imm) { + _dbg_assert_(reg != R_ZERO || imm == 0); + _dbg_assert_(reg >= X0 && reg <= X31); + // TODO: Could optimize this more for > 32 bit constants. + emit_->LI(reg, imm); + _dbg_assert_(!ar[reg].pointerified); +} + +void RiscVRegCache::MapRegTo(RiscVReg reg, IRRegIndex mipsReg, int mapFlags) { + _dbg_assert_(reg > X0 && reg <= X31); + _dbg_assert_(IsValidReg(mipsReg)); + _dbg_assert_(!mr[mipsReg].isStatic); + if (mr[mipsReg].isStatic) { + ERROR_LOG(JIT, "Cannot MapRegTo static register %d", mipsReg); + return; + } + ar[reg].isDirty = (mapFlags & MAP_DIRTY) != 0; + if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { + if (mipsReg == MIPS_REG_ZERO) { + // If we get a request to load the zero register, at least we won't spend + // time on a memory access... + emit_->LI(reg, 0); + + // This way, if we SetImm() it, we'll keep it. + mr[mipsReg].loc = MIPSLoc::RVREG_IMM; + mr[mipsReg].imm = 0; + } else { + switch (mr[mipsReg].loc) { + case MIPSLoc::MEM: + emit_->LWU(reg, CTXREG, GetMipsRegOffset(mipsReg)); + mr[mipsReg].loc = MIPSLoc::RVREG; + break; + case MIPSLoc::IMM: + SetRegImm(reg, mr[mipsReg].imm); + // IMM is always dirty. + ar[reg].isDirty = true; + + // If we are mapping dirty, it means we're gonna overwrite. + // So the imm value is no longer valid. + if (mapFlags & MAP_DIRTY) + mr[mipsReg].loc = MIPSLoc::RVREG; + else + mr[mipsReg].loc = MIPSLoc::RVREG_IMM; + break; + case MIPSLoc::RVREG_IMM: + // If it's not dirty, we can keep it. + if (ar[reg].isDirty) + mr[mipsReg].loc = MIPSLoc::RVREG; + break; + default: + _assert_msg_(mr[mipsReg].loc != MIPSLoc::RVREG_AS_PTR, "MapRegTo with a pointer?"); + mr[mipsReg].loc = MIPSLoc::RVREG; + break; + } + } + } else { + _dbg_assert_(mipsReg != MIPS_REG_ZERO); + _dbg_assert_(ar[reg].isDirty); + mr[mipsReg].loc = MIPSLoc::RVREG; + } + ar[reg].mipsReg = mipsReg; + ar[reg].pointerified = false; + mr[mipsReg].reg = reg; +} + +RiscVReg RiscVRegCache::AllocateReg() { + int allocCount; + const RiscVReg *allocOrder = GetMIPSAllocationOrder(allocCount); + +allocate: + for (int i = 0; i < allocCount; i++) { + RiscVReg reg = allocOrder[i]; + + if (ar[reg].mipsReg == IRREG_INVALID && !ar[reg].tempLocked) { + return reg; + } + } + + // Still nothing. Let's spill a reg and goto 10. + // TODO: Use age or something to choose which register to spill? + // TODO: Spill dirty regs first? or opposite? + bool clobbered; + RiscVReg bestToSpill = FindBestToSpill(true, &clobbered); + if (bestToSpill == INVALID_REG) { + bestToSpill = FindBestToSpill(false, &clobbered); + } + + if (bestToSpill != INVALID_REG) { + if (clobbered) { + DiscardR(ar[bestToSpill].mipsReg); + } else { + FlushRiscVReg(bestToSpill); + } + // Now one must be free. + goto allocate; + } + + // Uh oh, we have all of them spilllocked.... + ERROR_LOG_REPORT(JIT, "Out of spillable registers near PC %08x", mips_->pc); + _assert_(bestToSpill != INVALID_REG); + return INVALID_REG; +} + +RiscVReg RiscVRegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) { + int allocCount; + const RiscVReg *allocOrder = GetMIPSAllocationOrder(allocCount); + + static const int UNUSED_LOOKAHEAD_OPS = 30; + + *clobbered = false; + for (int i = 0; i < allocCount; i++) { + RiscVReg reg = allocOrder[i]; + if (ar[reg].mipsReg != IRREG_INVALID && mr[ar[reg].mipsReg].spillLock) + continue; + if (ar[reg].tempLocked) + continue; + + // As it's in alloc-order, we know it's not static so we don't need to check for that. + + // TODO: Look for clobbering in the IRInst array with index? + + // Not awesome. A used reg. Let's try to avoid spilling. + // TODO: Actually check if we'd be spilling. + if (unusedOnly) { + continue; + } + + return reg; + } + + return INVALID_REG; +} + +RiscVReg RiscVRegCache::TryMapTempImm(IRRegIndex r) { + _dbg_assert_(IsValidReg(r)); + // If already mapped, no need for a temporary. + if (IsMapped(r)) { + return R(r); + } + + if (mr[r].loc == MIPSLoc::IMM) { + if (mr[r].imm == 0) { + return R_ZERO; + } + + // Try our luck - check for an exact match in another rvreg. + for (int i = 0; i < NUM_MIPSREG; ++i) { + if (mr[i].loc == MIPSLoc::RVREG_IMM && mr[i].imm == mr[r].imm) { + // Awesome, let's just use this reg. + return mr[i].reg; + } + } + } + + return INVALID_REG; +} + +RiscVReg RiscVRegCache::GetAndLockTempR() { + RiscVReg reg = AllocateReg(); + if (reg != INVALID_REG) { + ar[reg].tempLocked = true; + } + return reg; +} + +RiscVReg RiscVRegCache::MapReg(IRRegIndex mipsReg, int mapFlags) { + _dbg_assert_(IsValidReg(mipsReg)); + + // TODO: Optimization to force HI/LO to be combined? + + if (mipsReg == IRREG_INVALID) { + ERROR_LOG(JIT, "Cannot map invalid register"); + return INVALID_REG; + } + + RiscVReg riscvReg = mr[mipsReg].reg; + + if (mr[mipsReg].isStatic) { + _dbg_assert_(riscvReg != INVALID_REG); + if (riscvReg == INVALID_REG) { + ERROR_LOG(JIT, "MapReg on statically mapped reg %d failed - riscvReg got lost", mipsReg); + } + if (mr[mipsReg].loc == MIPSLoc::IMM) { + // Back into the register, with or without the imm value. + // If noinit, the MAP_DIRTY check below will take care of the rest. + if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { + SetRegImm(riscvReg, mr[mipsReg].imm); + mr[mipsReg].loc = MIPSLoc::RVREG_IMM; + ar[riscvReg].pointerified = false; + } + } else if (mr[mipsReg].loc == MIPSLoc::RVREG_AS_PTR) { + // Was mapped as pointer, now we want it mapped as a value, presumably to + // add or subtract stuff to it. + if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { +#ifdef MASKED_PSP_MEMORY + _dbg_assert_(!ar[riscvReg].isDirty && (mapFlags & MAP_DIRTY) == 0); +#endif + emit_->SUB(riscvReg, riscvReg, MEMBASEREG); + } + mr[mipsReg].loc = MIPSLoc::RVREG; + } + // Erasing the imm on dirty (necessary since otherwise we will still think it's ML_RVREG_IMM and return + // true for IsImm and calculate crazily wrong things). /unknown + if (mapFlags & MAP_DIRTY) { + // As we are dirty, can't keep RVREG_IMM, we will quickly drift out of sync + mr[mipsReg].loc = MIPSLoc::RVREG; + ar[riscvReg].pointerified = false; + ar[riscvReg].isDirty = true; + } + return mr[mipsReg].reg; + } + + // Let's see if it's already mapped. If so we just need to update the dirty flag. + // We don't need to check for ML_NOINIT because we assume that anyone who maps + // with that flag immediately writes a "known" value to the register. + if (mr[mipsReg].loc == MIPSLoc::RVREG || mr[mipsReg].loc == MIPSLoc::RVREG_IMM) { + _dbg_assert_(riscvReg != INVALID_REG && ar[riscvReg].mipsReg == mipsReg); + if (ar[riscvReg].mipsReg != mipsReg) { + ERROR_LOG_REPORT(JIT, "Register mapping out of sync! %i", mipsReg); + } + if (mapFlags & MAP_DIRTY) { + // Mapping dirty means the old imm value is invalid. + mr[mipsReg].loc = MIPSLoc::RVREG; + ar[riscvReg].isDirty = true; + // If reg is written to, pointerification is lost. + ar[riscvReg].pointerified = false; + } + + return mr[mipsReg].reg; + } else if (mr[mipsReg].loc == MIPSLoc::RVREG_AS_PTR) { + // Was mapped as pointer, now we want it mapped as a value, presumably to + // add or subtract stuff to it. + if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { +#ifdef MASKED_PSP_MEMORY + _dbg_assert_(!ar[riscvReg].isDirty && (mapFlags & MAP_DIRTY) == 0); +#endif + emit_->SUB(riscvReg, riscvReg, MEMBASEREG); + } + mr[mipsReg].loc = MIPSLoc::RVREG; + if (mapFlags & MAP_DIRTY) { + ar[riscvReg].isDirty = true; + } + return mr[mipsReg].reg; + } + + // Okay, not mapped, so we need to allocate an RV register. + RiscVReg reg = AllocateReg(); + if (reg != INVALID_REG) { + // Grab it, and load the value into it (if requested). + MapRegTo(reg, mipsReg, mapFlags); + } + + return reg; +} + +RiscVReg RiscVRegCache::MapRegAsPointer(IRRegIndex reg) { + _dbg_assert_(IsValidRegNoZero(reg)); + + // Already mapped. + if (mr[reg].loc == MIPSLoc::RVREG_AS_PTR) { + return mr[reg].reg; + } + + RiscVReg riscvReg = INVALID_REG; + if (mr[reg].loc != MIPSLoc::RVREG && mr[reg].loc != MIPSLoc::RVREG_IMM) { + riscvReg = MapReg(reg); + } else { + riscvReg = mr[reg].reg; + } + + if (mr[reg].loc == MIPSLoc::RVREG || mr[reg].loc == MIPSLoc::RVREG_IMM) { + // If there was an imm attached, discard it. + mr[reg].loc = MIPSLoc::RVREG; + if (!jo_->enablePointerify) { + // Convert to a pointer by adding the base and clearing off the top bits. + // If SP, we can probably avoid the top bit clear, let's play with that later. +#ifdef MASKED_PSP_MEMORY + // This destroys the value... + _dbg_assert_(!ar[riscvReg].isDirty); + emit_->SLLIW(riscvReg, riscvReg, 2); + emit_->SRLIW(riscvReg, riscvReg, 2); +#endif + emit_->ADD(riscvReg, riscvReg, MEMBASEREG); + mr[reg].loc = MIPSLoc::RVREG_AS_PTR; + } else if (!ar[riscvReg].pointerified) { + emit_->ADD(riscvReg, riscvReg, MEMBASEREG); + ar[riscvReg].pointerified = true; + } + } else { + ERROR_LOG(JIT, "MapRegAsPointer : MapReg failed to allocate a register?"); + } + return riscvReg; +} + +void RiscVRegCache::MapIn(IRRegIndex rs) { + MapReg(rs); +} + +void RiscVRegCache::MapInIn(IRRegIndex rd, IRRegIndex rs) { + SpillLock(rd, rs); + MapReg(rd); + MapReg(rs); + ReleaseSpillLock(rd, rs); +} + +void RiscVRegCache::MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad) { + SpillLock(rd, rs); + bool load = !avoidLoad || rd == rs; + MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT); + MapReg(rs); + ReleaseSpillLock(rd, rs); +} + +void RiscVRegCache::MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad) { + SpillLock(rd, rs, rt); + bool load = !avoidLoad || (rd == rs || rd == rt); + MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT); + MapReg(rt); + MapReg(rs); + ReleaseSpillLock(rd, rs, rt); +} + +void RiscVRegCache::MapDirtyDirtyIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, bool avoidLoad) { + SpillLock(rd1, rd2, rs); + bool load1 = !avoidLoad || rd1 == rs; + bool load2 = !avoidLoad || rd2 == rs; + MapReg(rd1, load1 ? MAP_DIRTY : MAP_NOINIT); + MapReg(rd2, load2 ? MAP_DIRTY : MAP_NOINIT); + MapReg(rs); + ReleaseSpillLock(rd1, rd2, rs); +} + +void RiscVRegCache::MapDirtyDirtyInIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, IRRegIndex rt, bool avoidLoad) { + SpillLock(rd1, rd2, rs, rt); + bool load1 = !avoidLoad || (rd1 == rs || rd1 == rt); + bool load2 = !avoidLoad || (rd2 == rs || rd2 == rt); + MapReg(rd1, load1 ? MAP_DIRTY : MAP_NOINIT); + MapReg(rd2, load2 ? MAP_DIRTY : MAP_NOINIT); + MapReg(rt); + MapReg(rs); + ReleaseSpillLock(rd1, rd2, rs, rt); +} + +void RiscVRegCache::FlushRiscVReg(RiscVReg r) { + _dbg_assert_(r > X0 && r <= X31); + _dbg_assert_(ar[r].mipsReg != MIPS_REG_ZERO); + _dbg_assert_(!mr[ar[r].mipsReg].isStatic); + if (r == INVALID_REG) { + ERROR_LOG(JIT, "FlushRiscVReg called on invalid register %d", r); + return; + } + if (ar[r].mipsReg == IRREG_INVALID) { + // Nothing to do, reg not mapped. + _dbg_assert_(!ar[r].isDirty); + return; + } + if (mr[ar[r].mipsReg].isStatic) { + ERROR_LOG(JIT, "Cannot FlushRiscVReg a statically mapped register"); + return; + } + auto &mreg = mr[ar[r].mipsReg]; + if (mreg.loc == MIPSLoc::RVREG_IMM || ar[r].mipsReg == MIPS_REG_ZERO) { + // We know its immediate value, no need to STR now. + mreg.loc = MIPSLoc::IMM; + mreg.reg = INVALID_REG; + } else { + if (mreg.loc == MIPSLoc::IMM || ar[r].isDirty) { + if (mreg.loc == MIPSLoc::RVREG_AS_PTR) { + // Unpointerify, in case dirty. +#ifdef MASKED_PSP_MEMORY + _dbg_assert_(!ar[r].isDirty && (mapFlags & MAP_DIRTY) == 0); +#endif + emit_->SUB(r, r, MEMBASEREG); + mreg.loc = MIPSLoc::RVREG; + } + RiscVReg storeReg = RiscVRegForFlush(ar[r].mipsReg); + if (storeReg != INVALID_REG) + emit_->SW(storeReg, CTXREG, GetMipsRegOffset(ar[r].mipsReg)); + } + mreg.loc = MIPSLoc::MEM; + mreg.reg = INVALID_REG; + mreg.imm = -1; + } + ar[r].isDirty = false; + ar[r].mipsReg = IRREG_INVALID; + ar[r].pointerified = false; +} + +void RiscVRegCache::DiscardR(IRRegIndex mipsReg) { + _dbg_assert_(IsValidRegNoZero(mipsReg)); + if (mr[mipsReg].isStatic) { + // Simply do nothing unless it's an IMM/RVREG_IMM/RVREG_AS_PTR, in case we just switch it over to RVREG, losing the value. + RiscVReg riscvReg = mr[mipsReg].reg; + _dbg_assert_(riscvReg != INVALID_REG); + if (mipsReg == MIPS_REG_ZERO) { + // Shouldn't happen, but in case it does. + mr[mipsReg].loc = MIPSLoc::RVREG_IMM; + mr[mipsReg].reg = R_ZERO; + mr[mipsReg].imm = 0; + } else if (mr[mipsReg].loc == MIPSLoc::RVREG_IMM || mr[mipsReg].loc == MIPSLoc::IMM || mr[mipsReg].loc == MIPSLoc::RVREG_AS_PTR) { + // Ignore the imm value, restore sanity + mr[mipsReg].loc = MIPSLoc::RVREG; + ar[riscvReg].pointerified = false; + ar[riscvReg].isDirty = false; + } + return; + } + const MIPSLoc prevLoc = mr[mipsReg].loc; + if (prevLoc == MIPSLoc::RVREG || prevLoc == MIPSLoc::RVREG_IMM || prevLoc == MIPSLoc::RVREG_AS_PTR) { + RiscVReg riscvReg = mr[mipsReg].reg; + _dbg_assert_(riscvReg != INVALID_REG); + ar[riscvReg].mipsReg = IRREG_INVALID; + ar[riscvReg].pointerified = false; + ar[riscvReg].isDirty = false; + mr[mipsReg].reg = INVALID_REG; + mr[mipsReg].loc = MIPSLoc::MEM; + mr[mipsReg].imm = -1; + } + if (prevLoc == MIPSLoc::IMM && mipsReg != MIPS_REG_ZERO) { + mr[mipsReg].loc = MIPSLoc::MEM; + mr[mipsReg].imm = -1; + } +} + +RiscVReg RiscVRegCache::RiscVRegForFlush(IRRegIndex r) { + _dbg_assert_(IsValidReg(r)); + if (mr[r].isStatic) + return INVALID_REG; // No flushing needed + + switch (mr[r].loc) { + case MIPSLoc::IMM: + if (r == MIPS_REG_ZERO) { + return INVALID_REG; + } + // Zero is super easy. + if (mr[r].imm == 0) { + return R_ZERO; + } + // Could we get lucky? Check for an exact match in another rvreg. + for (int i = 0; i < NUM_MIPSREG; ++i) { + if (mr[i].loc == MIPSLoc::RVREG_IMM && mr[i].imm == mr[r].imm) { + // Awesome, let's just store this reg. + return mr[i].reg; + } + } + return INVALID_REG; + + case MIPSLoc::RVREG: + case MIPSLoc::RVREG_IMM: + if (mr[r].reg == INVALID_REG) { + ERROR_LOG_REPORT(JIT, "RiscVRegForFlush: MipsReg %d had bad riscvReg", r); + return INVALID_REG; + } + // No need to flush if it's zero or not dirty. + if (r == MIPS_REG_ZERO || !ar[mr[r].reg].isDirty) { + return INVALID_REG; + } + // TODO: Lo/hi optimization? + return mr[r].reg; + + case MIPSLoc::RVREG_AS_PTR: + return INVALID_REG; + + case MIPSLoc::MEM: + return INVALID_REG; + + default: + ERROR_LOG_REPORT(JIT, "RiscVRegForFlush: MipsReg %d with invalid location %d", r, (int)mr[r].loc); + return INVALID_REG; + } +} + +void RiscVRegCache::FlushR(IRRegIndex r) { + _dbg_assert_(IsValidRegNoZero(r)); + if (mr[r].isStatic) { + ERROR_LOG(JIT, "Cannot flush static reg %d", r); + return; + } + + switch (mr[r].loc) { + case MIPSLoc::IMM: + // IMM is always "dirty". + // TODO: HI/LO optimization? + if (r != MIPS_REG_ZERO) { + // Try to optimize using a different reg. + RiscVReg storeReg = RiscVRegForFlush(r); + if (storeReg == INVALID_REG) { + SetRegImm(SCRATCH1, mr[r].imm); + storeReg = SCRATCH1; + } + emit_->SW(storeReg, CTXREG, GetMipsRegOffset(r)); + } + break; + + case MIPSLoc::RVREG: + case MIPSLoc::RVREG_IMM: + if (ar[mr[r].reg].isDirty) { + RiscVReg storeReg = RiscVRegForFlush(r); + if (storeReg != INVALID_REG) { + emit_->SW(storeReg, CTXREG, GetMipsRegOffset(r)); + } + ar[mr[r].reg].isDirty = false; + } + ar[mr[r].reg].mipsReg = IRREG_INVALID; + ar[mr[r].reg].pointerified = false; + break; + + case MIPSLoc::RVREG_AS_PTR: + if (ar[mr[r].reg].isDirty) { +#ifdef MASKED_PSP_MEMORY + // This is kinda bad, because we've cleared bits in it. + _dbg_assert_(!ar[mr[r].reg].isDirty); +#endif + emit_->SUB(mr[r].reg, mr[r].reg, MEMBASEREG); + // We set this so RiscVRegForFlush knows it's no longer a pointer. + mr[r].loc = MIPSLoc::RVREG; + RiscVReg storeReg = RiscVRegForFlush(r); + if (storeReg != INVALID_REG) { + emit_->SW(storeReg, CTXREG, GetMipsRegOffset(r)); + } + ar[mr[r].reg].isDirty = false; + } + ar[mr[r].reg].mipsReg = IRREG_INVALID; + break; + + case MIPSLoc::MEM: + // Already there, nothing to do. + break; + + default: + ERROR_LOG_REPORT(JIT, "FlushR: MipsReg %d with invalid location %d", r, (int)mr[r].loc); + break; + } + if (r == MIPS_REG_ZERO) { + mr[r].loc = MIPSLoc::RVREG_IMM; + mr[r].reg = R_ZERO; + mr[r].imm = 0; + } else { + mr[r].loc = MIPSLoc::MEM; + mr[r].reg = INVALID_REG; + mr[r].imm = -1; + } +} + +void RiscVRegCache::FlushAll() { + // Note: make sure not to change the registers when flushing: + // Branching code expects the armreg to retain its value. + + // TODO: HI/LO optimization? + + // Final pass to grab any that were left behind. + for (int i = 1; i < NUM_MIPSREG; i++) { + IRRegIndex mipsReg = IRRegIndex(i); + if (mr[i].isStatic) { + RiscVReg riscvReg = mr[i].reg; + // Cannot leave any IMMs in registers, not even ML_ARMREG_IMM, can confuse the regalloc later if this flush is mid-block + // due to an interpreter fallback that changes the register. + if (mr[i].loc == MIPSLoc::IMM) { + SetRegImm(mr[i].reg, mr[i].imm); + mr[i].loc = MIPSLoc::RVREG; + ar[riscvReg].pointerified = false; + } else if (mr[i].loc == MIPSLoc::RVREG_IMM) { + // The register already contains the immediate. + if (ar[riscvReg].pointerified) { + ERROR_LOG(JIT, "RVREG_IMM but pointerified. Wrong."); + ar[riscvReg].pointerified = false; + } + mr[i].loc = MIPSLoc::RVREG; + } else if (mr[i].loc == MIPSLoc::RVREG_AS_PTR) { +#ifdef MASKED_PSP_MEMORY + _dbg_assert_(!ar[riscvReg].isDirty); +#endif + emit_->SUB(riscvReg, riscvReg, MEMBASEREG); + mr[i].loc = MIPSLoc::RVREG; + } + if (i != MIPS_REG_ZERO && mr[i].reg == INVALID_REG) { + ERROR_LOG(JIT, "RV reg of static %i is invalid", i); + continue; + } + } else if (IsValidRegNoZero(mipsReg)) { + FlushR(mipsReg); + } + } + + int count = 0; + const StaticAllocation *allocs = GetStaticAllocations(count); + for (int i = 0; i < count; i++) { + if (allocs[i].pointerified && !ar[allocs[i].ar].pointerified && jo_->enablePointerify) { + // Re-pointerify + emit_->ADD(allocs[i].ar, allocs[i].ar, MEMBASEREG); + ar[allocs[i].ar].pointerified = true; + } else { + // If this register got pointerified on the way, mark it as not. + // This nis so that after save/reload (like in an interpreter fallback), + // it won't be regarded as such, as it may no longer be. + ar[allocs[i].ar].pointerified = false; + } + } + // Sanity check + for (int i = 0; i < NUM_RVREG; i++) { + if (ar[i].mipsReg != IRREG_INVALID && mr[ar[i].mipsReg].isStatic == false) { + ERROR_LOG_REPORT(JIT, "Flush fail: ar[%i].mipsReg=%i", i, ar[i].mipsReg); + } + } +} + +void RiscVRegCache::SetImm(IRRegIndex r, u64 immVal) { + _dbg_assert_(IsValidReg(r)); + if (r == MIPS_REG_ZERO && immVal != 0) { + ERROR_LOG_REPORT(JIT, "Trying to set immediate %08x to r0", (u32)immVal); + return; + } + + if (mr[r].loc == MIPSLoc::RVREG_IMM && mr[r].imm == immVal) { + // Already have that value, let's keep it in the reg. + return; + } + + // TODO: HI/LO optimization? + // All regs on the PSP are 32 bit, but LO we treat as HI:LO so is 64 full bits. + immVal = immVal & 0xFFFFFFFF; + + if (mr[r].isStatic) { + mr[r].loc = MIPSLoc::IMM; + mr[r].imm = immVal; + ar[mr[r].reg].pointerified = false; + // We do not change reg to INVALID_REG for obvious reasons.. + } else { + // Zap existing value if cached in a reg + if (mr[r].reg != INVALID_REG) { + ar[mr[r].reg].mipsReg = IRREG_INVALID; + ar[mr[r].reg].isDirty = false; + ar[mr[r].reg].pointerified = false; + } + mr[r].loc = MIPSLoc::IMM; + mr[r].imm = immVal; + mr[r].reg = INVALID_REG; + } +} + +bool RiscVRegCache::IsImm(IRRegIndex r) const { + _dbg_assert_(IsValidReg(r)); + if (r == MIPS_REG_ZERO) + return true; + else + return mr[r].loc == MIPSLoc::IMM || mr[r].loc == MIPSLoc::RVREG_IMM; +} + +bool RiscVRegCache::IsPureImm(IRRegIndex r) const { + _dbg_assert_(IsValidReg(r)); + if (r == MIPS_REG_ZERO) + return true; + else + return mr[r].loc == MIPSLoc::IMM; +} + +u64 RiscVRegCache::GetImm(IRRegIndex r) const { + _dbg_assert_(IsValidReg(r)); + if (r == MIPS_REG_ZERO) + return 0; + if (mr[r].loc != MIPSLoc::IMM && mr[r].loc != MIPSLoc::RVREG_IMM) { + ERROR_LOG_REPORT(JIT, "Trying to get imm from non-imm register %i", r); + } + return mr[r].imm; +} + +int RiscVRegCache::GetMipsRegOffset(IRRegIndex r) { + _dbg_assert_(IsValidReg(r)); + return r * 4; +} + +bool RiscVRegCache::IsValidReg(IRRegIndex r) const { + if (r < 0 || r >= NUM_MIPSREG) + return false; + + // See MIPSState for these offsets. + + // Don't allow FPU or VFPU regs here. + if (r >= 32 && r < 32 + 32 + 128) + return false; + // Also disallow VFPU temps. + if (r >= 224 && r < 224 + 16) + return false; + // Don't allow nextPC, etc. since it's probably a mistake. + if (r > 245) + return false; + // Don't allow PC either. + if (r == 241) + return false; + + return true; +} + +bool RiscVRegCache::IsValidRegNoZero(IRRegIndex r) const { + return IsValidReg(r) && r != MIPS_REG_ZERO; +} + +void RiscVRegCache::SpillLock(IRRegIndex r1, IRRegIndex r2, IRRegIndex r3, IRRegIndex r4) { + _dbg_assert_(IsValidReg(r1)); + _dbg_assert_(r2 == IRREG_INVALID || IsValidReg(r2)); + _dbg_assert_(r3 == IRREG_INVALID || IsValidReg(r3)); + _dbg_assert_(r4 == IRREG_INVALID || IsValidReg(r4)); + mr[r1].spillLock = true; + if (r2 != IRREG_INVALID) mr[r2].spillLock = true; + if (r3 != IRREG_INVALID) mr[r3].spillLock = true; + if (r4 != IRREG_INVALID) mr[r4].spillLock = true; +} + +void RiscVRegCache::ReleaseSpillLocksAndDiscardTemps() { + for (int i = 0; i < NUM_MIPSREG; i++) { + if (!mr[i].isStatic) + mr[i].spillLock = false; + } + for (int i = 0; i < NUM_RVREG; i++) { + ar[i].tempLocked = false; + } +} + +void RiscVRegCache::ReleaseSpillLock(IRRegIndex r1, IRRegIndex r2, IRRegIndex r3, IRRegIndex r4) { + _dbg_assert_(IsValidReg(r1)); + _dbg_assert_(r2 == IRREG_INVALID || IsValidReg(r2)); + _dbg_assert_(r3 == IRREG_INVALID || IsValidReg(r3)); + _dbg_assert_(r4 == IRREG_INVALID || IsValidReg(r4)); + if (!mr[r1].isStatic) + mr[r1].spillLock = false; + if (r2 != IRREG_INVALID && !mr[r2].isStatic) + mr[r2].spillLock = false; + if (r3 != IRREG_INVALID && !mr[r3].isStatic) + mr[r3].spillLock = false; + if (r4 != IRREG_INVALID && !mr[r4].isStatic) + mr[r4].spillLock = false; +} + +RiscVReg RiscVRegCache::R(IRRegIndex mipsReg) { + _dbg_assert_(IsValidReg(mipsReg)); + _dbg_assert_(mr[mipsReg].loc == MIPSLoc::RVREG || mr[mipsReg].loc == MIPSLoc::RVREG_IMM); + if (mr[mipsReg].loc == MIPSLoc::RVREG || mr[mipsReg].loc == MIPSLoc::RVREG_IMM) { + return mr[mipsReg].reg; + } else { + ERROR_LOG_REPORT(JIT, "Reg %i not in riscv reg", mipsReg); + return INVALID_REG; // BAAAD + } +} + +RiscVReg RiscVRegCache::RPtr(IRRegIndex mipsReg) { + _dbg_assert_(IsValidReg(mipsReg)); + _dbg_assert_(mr[mipsReg].loc == MIPSLoc::RVREG || mr[mipsReg].loc == MIPSLoc::RVREG_IMM || mr[mipsReg].loc == MIPSLoc::RVREG_AS_PTR); + if (mr[mipsReg].loc == MIPSLoc::RVREG_AS_PTR) { + return mr[mipsReg].reg; + } else if (mr[mipsReg].loc == MIPSLoc::RVREG || mr[mipsReg].loc == MIPSLoc::RVREG_IMM) { + int rv = mr[mipsReg].reg; + _dbg_assert_(ar[rv].pointerified); + if (ar[rv].pointerified) { + return mr[mipsReg].reg; + } else { + ERROR_LOG(JIT, "Tried to use a non-pointer register as a pointer"); + return INVALID_REG; + } + } else { + ERROR_LOG_REPORT(JIT, "Reg %i not in riscv reg", mipsReg); + return INVALID_REG; // BAAAD + } +} diff --git a/Core/MIPS/RiscV/RiscVRegCache.h b/Core/MIPS/RiscV/RiscVRegCache.h index a52eb95ec9..644687c40f 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.h +++ b/Core/MIPS/RiscV/RiscVRegCache.h @@ -18,6 +18,7 @@ #pragma once #include "Common/RiscVEmitter.h" +#include "Core/MIPS/MIPS.h" namespace RiscVJitConstants { @@ -32,4 +33,138 @@ const RiscVGen::RiscVReg MEMBASEREG = RiscVGen::X27; const RiscVGen::RiscVReg SCRATCH1 = RiscVGen::X10; const RiscVGen::RiscVReg SCRATCH2 = RiscVGen::X11; +// Have to account for all of them due to temps, etc. +constexpr int TOTAL_MAPPABLE_MIPSREGS = 256; + +enum class MIPSLoc { + IMM, + RVREG, + // In a native reg, but an adjusted pointer (not pointerified - unaligned.) + RVREG_AS_PTR, + // In a native reg, but also has a known immediate value. + RVREG_IMM, + MEM, +}; + +// Initing is the default so the flag is reversed. +enum { + MAP_DIRTY = 1, + MAP_NOINIT = 2 | MAP_DIRTY, +}; + } // namespace RiscVJitConstants + +namespace MIPSAnalyst { +struct AnalysisResults; +}; + +namespace MIPSComp { +struct JitOptions; +} + +// Not using IRReg since this can be -1. +typedef int IRRegIndex; +constexpr IRRegIndex IRREG_INVALID = -1; + +struct RegStatusRiscV { + IRRegIndex mipsReg; // if -1, no mipsreg attached. + bool isDirty; // Should the register be written back? + bool pointerified; // Has added the memory base into the top part of the reg. Note - still usable as 32-bit reg (in some cases.) + bool tempLocked; // Reserved for a temp register. +}; + +struct RegStatusMIPS { + // Where is this MIPS register? + RiscVJitConstants::MIPSLoc loc; + // Data (both or only one may be used, depending on loc.) + u64 imm; + RiscVGen::RiscVReg reg; // reg index + bool spillLock; // if true, this register cannot be spilled. + bool isStatic; // if true, this register will not be written back to ram by the regcache + // If loc == ML_MEM, it's back in its location in the CPU context struct. +}; + +class RiscVRegCache { +public: + RiscVRegCache(MIPSState *mipsState, MIPSComp::JitOptions *jo); + ~RiscVRegCache() {} + + void Init(RiscVGen::RiscVEmitter *emitter); + // TODO: Maybe pass in IR block and start PC for logging/debugging? + void Start(); + + // Protect the arm register containing a MIPS register from spilling, to ensure that + // it's being kept allocated. + void SpillLock(IRRegIndex reg, IRRegIndex reg2 = IRREG_INVALID, IRRegIndex reg3 = IRREG_INVALID, IRRegIndex reg4 = IRREG_INVALID); + void ReleaseSpillLock(IRRegIndex reg, IRRegIndex reg2 = IRREG_INVALID, IRRegIndex reg3 = IRREG_INVALID, IRRegIndex reg4 = IRREG_INVALID); + void ReleaseSpillLocksAndDiscardTemps(); + + void SetImm(IRRegIndex reg, u64 immVal); + bool IsImm(IRRegIndex reg) const; + bool IsPureImm(IRRegIndex reg) const; + u64 GetImm(IRRegIndex reg) const; + // Optimally set a register to an imm value (possibly using another register.) + void SetRegImm(RiscVGen::RiscVReg reg, u64 imm); + + // May fail and return INVALID_REG if it needs flushing. + RiscVGen::RiscVReg TryMapTempImm(IRRegIndex); + + // Returns an ARM register containing the requested MIPS register. + RiscVGen::RiscVReg MapReg(IRRegIndex reg, int mapFlags = 0); + RiscVGen::RiscVReg MapRegAsPointer(IRRegIndex reg); + + bool IsMapped(IRRegIndex reg); + bool IsMappedAsPointer(IRRegIndex reg); + bool IsInRAM(IRRegIndex reg); + + void MarkDirty(RiscVGen::RiscVReg reg); + void MapIn(IRRegIndex rs); + void MapInIn(IRRegIndex rd, IRRegIndex rs); + void MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad = true); + void MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad = true); + void MapDirtyDirtyIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, bool avoidLoad = true); + void MapDirtyDirtyInIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, IRRegIndex rt, bool avoidLoad = true); + void FlushRiscVReg(RiscVGen::RiscVReg r); + void FlushBeforeCall(); + void FlushAll(); + void FlushR(IRRegIndex r); + void DiscardR(IRRegIndex r); + + RiscVGen::RiscVReg GetAndLockTempR(); + + RiscVGen::RiscVReg R(IRRegIndex preg); // Returns a cached register, while checking that it's NOT mapped as a pointer + RiscVGen::RiscVReg RPtr(IRRegIndex preg); // Returns a cached register, if it has been mapped as a pointer + + // These are called once on startup to generate functions, that you should then call. + void EmitLoadStaticRegisters(); + void EmitSaveStaticRegisters(); + +private: + struct StaticAllocation { + IRRegIndex mr; + RiscVGen::RiscVReg ar; + bool pointerified; + }; + const StaticAllocation *GetStaticAllocations(int &count); + const RiscVGen::RiscVReg *GetMIPSAllocationOrder(int &count); + void MapRegTo(RiscVGen::RiscVReg reg, IRRegIndex mipsReg, int mapFlags); + RiscVGen::RiscVReg AllocateReg(); + RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered); + RiscVGen::RiscVReg RiscVRegForFlush(IRRegIndex r); + int GetMipsRegOffset(IRRegIndex r); + + bool IsValidReg(IRRegIndex r) const; + bool IsValidRegNoZero(IRRegIndex r) const; + + MIPSState *mips_; + RiscVGen::RiscVEmitter *emit_ = nullptr; + MIPSComp::JitOptions *jo_; + + enum { + NUM_RVREG = 32, // 31 actual registers, plus the zero/sp register which is not mappable. + NUM_MIPSREG = RiscVJitConstants::TOTAL_MAPPABLE_MIPSREGS, + }; + + RegStatusRiscV ar[NUM_RVREG]{}; + RegStatusMIPS mr[NUM_MIPSREG]{}; +}; diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp new file mode 100644 index 0000000000..7287ea10b7 --- /dev/null +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp @@ -0,0 +1,16 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.h b/Core/MIPS/RiscV/RiscVRegCacheFPU.h new file mode 100644 index 0000000000..48b5ab53e0 --- /dev/null +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.h @@ -0,0 +1,18 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once From bf7a6eb2cd313942e3144976c86db0dca0f66d8d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 20 Jul 2023 21:09:59 -0700 Subject: [PATCH 068/167] riscv: Add jit for some initial instructions. --- CMakeLists.txt | 6 ++ Core/Core.vcxproj | 6 ++ Core/Core.vcxproj.filters | 18 ++++ Core/MIPS/RiscV/RiscVCompALU.cpp | 137 +++++++++++++++++++++++++ Core/MIPS/RiscV/RiscVCompBranch.cpp | 60 +++++++++++ Core/MIPS/RiscV/RiscVCompFPU.cpp | 37 +++++++ Core/MIPS/RiscV/RiscVCompLoadStore.cpp | 37 +++++++ Core/MIPS/RiscV/RiscVCompSystem.cpp | 70 +++++++++++++ Core/MIPS/RiscV/RiscVCompVec.cpp | 37 +++++++ Core/MIPS/RiscV/RiscVJit.cpp | 60 ++++++++++- Core/MIPS/RiscV/RiscVJit.h | 7 ++ 11 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 Core/MIPS/RiscV/RiscVCompALU.cpp create mode 100644 Core/MIPS/RiscV/RiscVCompBranch.cpp create mode 100644 Core/MIPS/RiscV/RiscVCompFPU.cpp create mode 100644 Core/MIPS/RiscV/RiscVCompLoadStore.cpp create mode 100644 Core/MIPS/RiscV/RiscVCompSystem.cpp create mode 100644 Core/MIPS/RiscV/RiscVCompVec.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7aeeb85377..cb3303d8cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1610,6 +1610,12 @@ list(APPEND CoreExtra list(APPEND CoreExtra Core/MIPS/RiscV/RiscVAsm.cpp + Core/MIPS/RiscV/RiscVCompALU.cpp + Core/MIPS/RiscV/RiscVCompBranch.cpp + Core/MIPS/RiscV/RiscVCompFPU.cpp + Core/MIPS/RiscV/RiscVCompLoadStore.cpp + Core/MIPS/RiscV/RiscVCompSystem.cpp + Core/MIPS/RiscV/RiscVCompVec.cpp Core/MIPS/RiscV/RiscVJit.cpp Core/MIPS/RiscV/RiscVJit.h Core/MIPS/RiscV/RiscVRegCache.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index fae61b7fbd..b2ae24174f 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -594,6 +594,12 @@ + + + + + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 910a58a806..a4f56f5eb1 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1219,6 +1219,24 @@ MIPS\RiscV + + MIPS\RiscV + + + MIPS\RiscV + + + MIPS\RiscV + + + MIPS\RiscV + + + MIPS\RiscV + + + MIPS\RiscV + diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp new file mode 100644 index 0000000000..bce0334489 --- /dev/null +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -0,0 +1,137 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/MemMap.h" +#include "Core/MIPS/RiscV/RiscVJit.h" +#include "Core/MIPS/RiscV/RiscVRegCache.h" + +// This file contains compilation for integer / arithmetic / logic related instructions. +// +// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. +// Currently known non working ones should have DISABLE. No flags because that's in IR already. + +// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; } +#define CONDITIONAL_DISABLE {} +#define DISABLE { CompIR_Generic(inst); return; } +#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; } + +namespace MIPSComp { + +using namespace RiscVGen; +using namespace RiscVJitConstants; + +void RiscVJit::CompIR_Arith(IRInst inst) { + CONDITIONAL_DISABLE; + + bool allowPtrMath = true; +#ifndef MASKED_PSP_MEMORY + // Since we modify it, we can't safely. + allowPtrMath = false; +#endif + + // RISC-V only adds signed immediates, so rewrite a small enough subtract to an add. + // We use -2047 and 2048 here because the range swaps. + if (inst.op == IROp::SubConst && (int32_t)inst.constant >= -2047 && (int32_t)inst.constant <= 2048) { + inst.op = IROp::AddConst; + inst.constant = (uint32_t)-(int32_t)inst.constant; + } + + switch (inst.op) { + case IROp::Add: + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + ADDW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + break; + + case IROp::Sub: + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + SUBW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + break; + + case IROp::AddConst: + if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) { + // Typical of stack pointer updates. + if (gpr.IsMappedAsPointer(inst.src1) && inst.dest == inst.src1 && allowPtrMath) { + gpr.MarkDirty(gpr.RPtr(inst.dest)); + ADDI(gpr.RPtr(inst.dest), gpr.RPtr(inst.dest), inst.constant); + } else { + gpr.MapDirtyIn(inst.dest, inst.src1); + ADDIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant); + } + } else { + gpr.MapDirtyIn(inst.dest, inst.src1); + LI(SCRATCH1, (s32)inst.constant, SCRATCH2); + ADDW(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1); + } + break; + + case IROp::SubConst: + gpr.MapDirtyIn(inst.dest, inst.src1); + LI(SCRATCH1, (s32)inst.constant, SCRATCH2); + SUBW(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1); + break; + + case IROp::Neg: + gpr.MapDirtyIn(inst.dest, inst.src1); + SUBW(gpr.R(inst.dest), R_ZERO, gpr.R(inst.src1)); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_Logic(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::And: + case IROp::Or: + case IROp::Xor: + case IROp::AndConst: + case IROp::OrConst: + case IROp::XorConst: + case IROp::Not: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_Assign(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Mov: + case IROp::Ext8to32: + case IROp::Ext16to32: + case IROp::ReverseBits: + case IROp::BSwap16: + case IROp::BSwap32: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +} // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVCompBranch.cpp b/Core/MIPS/RiscV/RiscVCompBranch.cpp new file mode 100644 index 0000000000..6e158cfc0d --- /dev/null +++ b/Core/MIPS/RiscV/RiscVCompBranch.cpp @@ -0,0 +1,60 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/MemMap.h" +#include "Core/MIPS/RiscV/RiscVJit.h" +#include "Core/MIPS/RiscV/RiscVRegCache.h" + +// This file contains compilation for exits, syscalls, and related. +// +// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. +// Currently known non working ones should have DISABLE. No flags because that's in IR already. + +// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; } +#define CONDITIONAL_DISABLE {} +#define DISABLE { CompIR_Generic(inst); return; } +#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; } + +namespace MIPSComp { + +using namespace RiscVGen; +using namespace RiscVJitConstants; + +void RiscVJit::CompIR_Exit(IRInst inst) { + CONDITIONAL_DISABLE; + + // Will probably always want this? + FlushAll(); + switch (inst.op) { + case IROp::ExitToConst: + case IROp::ExitToReg: + case IROp::ExitToConstIfEq: + case IROp::ExitToConstIfNeq: + case IROp::ExitToConstIfGeZ: + case IROp::ExitToConstIfLtZ: + case IROp::ExitToConstIfLeZ: + case IROp::ExitToPC: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +} // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp new file mode 100644 index 0000000000..61fb11d0e0 --- /dev/null +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/MemMap.h" +#include "Core/MIPS/RiscV/RiscVJit.h" +#include "Core/MIPS/RiscV/RiscVRegCache.h" + +// This file contains compilation for floating point related instructions. +// +// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. +// Currently known non working ones should have DISABLE. No flags because that's in IR already. + +// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; } +#define CONDITIONAL_DISABLE {} +#define DISABLE { CompIR_Generic(inst); return; } +#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; } + +namespace MIPSComp { + +using namespace RiscVGen; +using namespace RiscVJitConstants; + +} // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp new file mode 100644 index 0000000000..11b19b0c35 --- /dev/null +++ b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/MemMap.h" +#include "Core/MIPS/RiscV/RiscVJit.h" +#include "Core/MIPS/RiscV/RiscVRegCache.h" + +// This file contains compilation for load/store instructions. +// +// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. +// Currently known non working ones should have DISABLE. No flags because that's in IR already. + +// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; } +#define CONDITIONAL_DISABLE {} +#define DISABLE { CompIR_Generic(inst); return; } +#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; } + +namespace MIPSComp { + +using namespace RiscVGen; +using namespace RiscVJitConstants; + +} // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVCompSystem.cpp b/Core/MIPS/RiscV/RiscVCompSystem.cpp new file mode 100644 index 0000000000..b5e5af3b24 --- /dev/null +++ b/Core/MIPS/RiscV/RiscVCompSystem.cpp @@ -0,0 +1,70 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/MemMap.h" +#include "Core/MIPS/RiscV/RiscVJit.h" +#include "Core/MIPS/RiscV/RiscVRegCache.h" + +// This file contains compilation for basic PC/downcount accounting, debug funcs, etc. +// +// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. +// Currently known non working ones should have DISABLE. No flags because that's in IR already. + +// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; } +#define CONDITIONAL_DISABLE {} +#define DISABLE { CompIR_Generic(inst); return; } +#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; } + +namespace MIPSComp { + +using namespace RiscVGen; +using namespace RiscVJitConstants; + +void RiscVJit::CompIR_Basic(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::SetConst: + gpr.SetImm(inst.dest, inst.constant); + break; + + case IROp::Downcount: + if (inst.constant <= 2048) { + ADDI(DOWNCOUNTREG, DOWNCOUNTREG, -(s32)inst.constant); + } else { + LI(SCRATCH1, inst.constant, SCRATCH2); + SUB(DOWNCOUNTREG, DOWNCOUNTREG, SCRATCH1); + } + break; + + case IROp::SetPC: + gpr.MapIn(inst.src1); + MovToPC(gpr.R(inst.src1)); + break; + + case IROp::SetPCConst: + LI(SCRATCH1, inst.constant, SCRATCH2); + MovToPC(SCRATCH1); + break; + + default: + INVALIDOP; + break; + } +} + +} // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVCompVec.cpp b/Core/MIPS/RiscV/RiscVCompVec.cpp new file mode 100644 index 0000000000..8ccc3f129a --- /dev/null +++ b/Core/MIPS/RiscV/RiscVCompVec.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2023- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/MemMap.h" +#include "Core/MIPS/RiscV/RiscVJit.h" +#include "Core/MIPS/RiscV/RiscVRegCache.h" + +// This file contains compilation for vector instructions. +// +// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. +// Currently known non working ones should have DISABLE. No flags because that's in IR already. + +// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; } +#define CONDITIONAL_DISABLE {} +#define DISABLE { CompIR_Generic(inst); return; } +#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; } + +namespace MIPSComp { + +using namespace RiscVGen; +using namespace RiscVJitConstants; + +} // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 8f5608ac4e..7736188706 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -65,7 +65,8 @@ bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u // TODO: Block linking, checked entries and such. int block_num = blocks_.GetBlockNumberFromStartAddress(em_address); - _assert_msg_(blockStartAddrs_[block_num] == nullptr, "Block reused before clear"); + _assert_msg_(block_num >= 0 && block_num < MAX_ALLOWED_JIT_BLOCKS, "Bad block num"); + _assert_msg_(blockStartAddrs_[block_num] == nullptr, "Block %d reused before clear", block_num); blockStartAddrs_[block_num] = GetCodePointer(); gpr.Start(); @@ -106,7 +107,64 @@ static u32 DoIRInst(uint64_t value) { } void RiscVJit::CompileIRInst(IRInst inst) { + switch (inst.op) { + case IROp::Nop: + break; + + case IROp::SetConst: + case IROp::Downcount: + case IROp::SetPC: + case IROp::SetPCConst: + CompIR_Basic(inst); + break; + + case IROp::Add: + case IROp::Sub: + case IROp::AddConst: + case IROp::SubConst: + case IROp::Neg: + CompIR_Arith(inst); + break; + + case IROp::And: + case IROp::Or: + case IROp::Xor: + case IROp::AndConst: + case IROp::OrConst: + case IROp::XorConst: + case IROp::Not: + CompIR_Logic(inst); + break; + + case IROp::Mov: + case IROp::Ext8to32: + case IROp::Ext16to32: + case IROp::ReverseBits: + case IROp::BSwap16: + case IROp::BSwap32: + CompIR_Assign(inst); + break; + + case IROp::ExitToConst: + case IROp::ExitToReg: + case IROp::ExitToConstIfEq: + case IROp::ExitToConstIfNeq: + case IROp::ExitToConstIfGeZ: + case IROp::ExitToConstIfLtZ: + case IROp::ExitToConstIfLeZ: + case IROp::ExitToPC: + CompIR_Exit(inst); + break; + + default: + CompIR_Generic(inst); + break; + } +} + +void RiscVJit::CompIR_Generic(IRInst inst) { // For now, we're gonna do it the slow and ugly way. + // Maybe there's a smarter way to fallback? uint64_t value; memcpy(&value, &inst, sizeof(inst)); diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index 13e4e6cf71..1467d80b3e 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -63,6 +63,13 @@ private: void FlushAll(); + void CompIR_Generic(IRInst inst); + void CompIR_Basic(IRInst inst); + void CompIR_Arith(IRInst inst); + void CompIR_Logic(IRInst inst); + void CompIR_Assign(IRInst inst); + void CompIR_Exit(IRInst inst); + RiscVRegCache gpr; const u8 *enterDispatcher_ = nullptr; From c6c25af484c424f032b0a5b81abe6abd17bcdb24 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 20 Jul 2023 23:37:34 -0700 Subject: [PATCH 069/167] riscv: Add some safety to pointerifying. We have to clear the upper bits in case of sign extension or other things. --- Core/MIPS/RiscV/RiscVRegCache.cpp | 36 ++++++++++++++++++++++--------- Core/MIPS/RiscV/RiscVRegCache.h | 1 + 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index 20aff331fa..b209141cb7 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -15,6 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include "Common/CPUDetect.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" #include "Core/MIPS/JitCommon/JitState.h" #include "Core/Reporting.h" @@ -446,16 +447,10 @@ RiscVReg RiscVRegCache::MapRegAsPointer(IRRegIndex reg) { if (!jo_->enablePointerify) { // Convert to a pointer by adding the base and clearing off the top bits. // If SP, we can probably avoid the top bit clear, let's play with that later. -#ifdef MASKED_PSP_MEMORY - // This destroys the value... - _dbg_assert_(!ar[riscvReg].isDirty); - emit_->SLLIW(riscvReg, riscvReg, 2); - emit_->SRLIW(riscvReg, riscvReg, 2); -#endif - emit_->ADD(riscvReg, riscvReg, MEMBASEREG); + AddMemBase(riscvReg); mr[reg].loc = MIPSLoc::RVREG_AS_PTR; } else if (!ar[riscvReg].pointerified) { - emit_->ADD(riscvReg, riscvReg, MEMBASEREG); + AddMemBase(riscvReg); ar[riscvReg].pointerified = true; } } else { @@ -464,6 +459,27 @@ RiscVReg RiscVRegCache::MapRegAsPointer(IRRegIndex reg) { return riscvReg; } +void RiscVRegCache::AddMemBase(RiscVGen::RiscVReg reg) { + _assert_(reg >= X0 && reg <= X31); +#ifdef MASKED_PSP_MEMORY + // This destroys the value... + _dbg_assert_(!ar[reg].isDirty); + emit_->SLLIW(reg, reg, 2); + emit_->SRLIW(reg, reg, 2); + emit_->ADD(reg, reg, MEMBASEREG); +#else + // Clear the top bits to be safe. + if (cpu_info.RiscV_Zba) { + emit_->ADD_UW(reg, reg, MEMBASEREG); + } else { + _assert_(XLEN == 64); + emit_->SLLI(reg, reg, 32); + emit_->SRLI(reg, reg, 32); + emit_->ADD(reg, reg, MEMBASEREG); + } +#endif +} + void RiscVRegCache::MapIn(IRRegIndex rs) { MapReg(rs); } @@ -759,11 +775,11 @@ void RiscVRegCache::FlushAll() { for (int i = 0; i < count; i++) { if (allocs[i].pointerified && !ar[allocs[i].ar].pointerified && jo_->enablePointerify) { // Re-pointerify - emit_->ADD(allocs[i].ar, allocs[i].ar, MEMBASEREG); + AddMemBase(allocs[i].ar); ar[allocs[i].ar].pointerified = true; } else { // If this register got pointerified on the way, mark it as not. - // This nis so that after save/reload (like in an interpreter fallback), + // This is so that after save/reload (like in an interpreter fallback), // it won't be regarded as such, as it may no longer be. ar[allocs[i].ar].pointerified = false; } diff --git a/Core/MIPS/RiscV/RiscVRegCache.h b/Core/MIPS/RiscV/RiscVRegCache.h index 644687c40f..326332d620 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.h +++ b/Core/MIPS/RiscV/RiscVRegCache.h @@ -151,6 +151,7 @@ private: RiscVGen::RiscVReg AllocateReg(); RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered); RiscVGen::RiscVReg RiscVRegForFlush(IRRegIndex r); + void AddMemBase(RiscVGen::RiscVReg reg); int GetMipsRegOffset(IRRegIndex r); bool IsValidReg(IRRegIndex r) const; From 05a2789cf48871ae9fc65814eff8000c4b587574 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 20 Jul 2023 23:55:45 -0700 Subject: [PATCH 070/167] riscv: Implement some simple assign instructions. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 44 ++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index bce0334489..125c74fa51 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -15,6 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include "Common/CPUDetect.h" #include "Core/MemMap.h" #include "Core/MIPS/RiscV/RiscVJit.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" @@ -120,14 +121,53 @@ void RiscVJit::CompIR_Assign(IRInst inst) { switch (inst.op) { case IROp::Mov: + gpr.MapDirtyIn(inst.dest, inst.src1); + MV(gpr.R(inst.dest), gpr.R(inst.src1)); + break; + case IROp::Ext8to32: + if (cpu_info.RiscV_Zbb) { + gpr.MapDirtyIn(inst.dest, inst.src1); + SEXT_B(gpr.R(inst.dest), gpr.R(inst.src1)); + } else { + gpr.MapDirtyIn(inst.dest, inst.src1); + SLLI(gpr.R(inst.dest), gpr.R(inst.src1), 24); + SRAIW(gpr.R(inst.dest), gpr.R(inst.dest), 24); + } + break; + case IROp::Ext16to32: + if (cpu_info.RiscV_Zbb) { + gpr.MapDirtyIn(inst.dest, inst.src1); + SEXT_H(gpr.R(inst.dest), gpr.R(inst.src1)); + } else { + gpr.MapDirtyIn(inst.dest, inst.src1); + SLLI(gpr.R(inst.dest), gpr.R(inst.src1), 16); + SRAIW(gpr.R(inst.dest), gpr.R(inst.dest), 16); + } + break; + case IROp::ReverseBits: - case IROp::BSwap16: - case IROp::BSwap32: CompIR_Generic(inst); break; + case IROp::BSwap16: + CompIR_Generic(inst); + break; + + case IROp::BSwap32: + if (cpu_info.RiscV_Zbb) { + gpr.MapDirtyIn(inst.dest, inst.src1); + REV8(gpr.R(inst.dest), gpr.R(inst.src1)); + if (XLEN >= 64) { + // REV8 swaps the entire register, so get the 32 highest bits. + SRLI(gpr.R(inst.dest), gpr.R(inst.dest), XLEN - 32); + } + } else { + CompIR_Generic(inst); + } + break; + default: INVALIDOP; break; From c2da7d18bb19e9ad8a52a2feb22219b50c680ba5 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 21 Jul 2023 20:29:35 -0700 Subject: [PATCH 071/167] riscv: Stub out more IR compilation categories. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 120 +++++++++++++ Core/MIPS/RiscV/RiscVCompBranch.cpp | 28 ++- Core/MIPS/RiscV/RiscVCompFPU.cpp | 149 ++++++++++++++++ Core/MIPS/RiscV/RiscVCompLoadStore.cpp | 120 +++++++++++++ Core/MIPS/RiscV/RiscVCompSystem.cpp | 73 ++++++++ Core/MIPS/RiscV/RiscVCompVec.cpp | 86 +++++++++ Core/MIPS/RiscV/RiscVJit.cpp | 234 ++++++++++++++++++++++++- Core/MIPS/RiscV/RiscVJit.h | 40 ++++- 8 files changed, 836 insertions(+), 14 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index 125c74fa51..2e4a7a248b 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -147,6 +147,16 @@ void RiscVJit::CompIR_Assign(IRInst inst) { } break; + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_Bits(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { case IROp::ReverseBits: CompIR_Generic(inst); break; @@ -168,6 +178,116 @@ void RiscVJit::CompIR_Assign(IRInst inst) { } break; + case IROp::Clz: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_Shift(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Shl: + case IROp::Shr: + case IROp::Sar: + case IROp::Ror: + case IROp::ShlImm: + case IROp::ShrImm: + case IROp::SarImm: + case IROp::RorImm: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_Compare(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Slt: + case IROp::SltConst: + case IROp::SltU: + case IROp::SltUConst: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_CondAssign(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::MovZ: + case IROp::MovNZ: + case IROp::Max: + case IROp::Min: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_HiLo(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::MtLo: + case IROp::MtHi: + case IROp::MfLo: + case IROp::MfHi: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_Mult(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Mult: + case IROp::MultU: + case IROp::Madd: + case IROp::MaddU: + case IROp::Msub: + case IROp::MsubU: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_Div(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Div: + case IROp::DivU: + CompIR_Generic(inst); + break; + default: INVALIDOP; break; diff --git a/Core/MIPS/RiscV/RiscVCompBranch.cpp b/Core/MIPS/RiscV/RiscVCompBranch.cpp index 6e158cfc0d..a14ee49d0c 100644 --- a/Core/MIPS/RiscV/RiscVCompBranch.cpp +++ b/Core/MIPS/RiscV/RiscVCompBranch.cpp @@ -42,11 +42,6 @@ void RiscVJit::CompIR_Exit(IRInst inst) { switch (inst.op) { case IROp::ExitToConst: case IROp::ExitToReg: - case IROp::ExitToConstIfEq: - case IROp::ExitToConstIfNeq: - case IROp::ExitToConstIfGeZ: - case IROp::ExitToConstIfLtZ: - case IROp::ExitToConstIfLeZ: case IROp::ExitToPC: CompIR_Generic(inst); break; @@ -57,4 +52,27 @@ void RiscVJit::CompIR_Exit(IRInst inst) { } } +void RiscVJit::CompIR_ExitIf(IRInst inst) { + CONDITIONAL_DISABLE; + + // Will probably always want this? + FlushAll(); + switch (inst.op) { + case IROp::ExitToConstIfEq: + case IROp::ExitToConstIfNeq: + case IROp::ExitToConstIfGtZ: + case IROp::ExitToConstIfGeZ: + case IROp::ExitToConstIfLtZ: + case IROp::ExitToConstIfLeZ: + case IROp::ExitToConstIfFpTrue: + case IROp::ExitToConstIfFpFalse: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + } // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index 61fb11d0e0..ef8e6a8db8 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -34,4 +34,153 @@ namespace MIPSComp { using namespace RiscVGen; using namespace RiscVJitConstants; +void RiscVJit::CompIR_FArith(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::FAdd: + case IROp::FSub: + case IROp::FMul: + case IROp::FDiv: + case IROp::FSqrt: + case IROp::FNeg: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_FCondAssign(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::FMin: + case IROp::FMax: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_FAssign(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::FMov: + case IROp::FAbs: + case IROp::FSign: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_FRound(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::FRound: + case IROp::FTrunc: + case IROp::FCeil: + case IROp::FFloor: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_FCvt(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::FCvtWS: + case IROp::FCvtSW: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_FSat(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::FSat0_1: + case IROp::FSatMinus1_1: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_FCompare(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::ZeroFpCond: + case IROp::FCmp: + case IROp::FCmovVfpuCC: + case IROp::FCmpVfpuBit: + case IROp::FCmpVfpuAggregate: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_RoundingMode(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::RestoreRoundingMode: + case IROp::ApplyRoundingMode: + case IROp::UpdateRoundingMode: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_FSpecial(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::FSin: + case IROp::FCos: + case IROp::FRSqrt: + case IROp::FRecip: + case IROp::FAsin: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + } // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp index 11b19b0c35..13c9437fa4 100644 --- a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp +++ b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp @@ -34,4 +34,124 @@ namespace MIPSComp { using namespace RiscVGen; using namespace RiscVJitConstants; +void RiscVJit::CompIR_Load(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Load8: + case IROp::Load8Ext: + case IROp::Load16: + case IROp::Load16Ext: + case IROp::Load32: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_LoadShift(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Load32Left: + case IROp::Load32Right: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_FLoad(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::LoadFloat: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_VecLoad(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::LoadVec4: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_Store(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Store8: + case IROp::Store16: + case IROp::Store32: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_StoreShift(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Store32Left: + case IROp::Store32Right: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_FStore(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::StoreFloat: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_VecStore(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::StoreVec4: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + } // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVCompSystem.cpp b/Core/MIPS/RiscV/RiscVCompSystem.cpp index b5e5af3b24..d097915d0c 100644 --- a/Core/MIPS/RiscV/RiscVCompSystem.cpp +++ b/Core/MIPS/RiscV/RiscVCompSystem.cpp @@ -42,6 +42,10 @@ void RiscVJit::CompIR_Basic(IRInst inst) { gpr.SetImm(inst.dest, inst.constant); break; + case IROp::SetConstF: + CompIR_Generic(inst); + break; + case IROp::Downcount: if (inst.constant <= 2048) { ADDI(DOWNCOUNTREG, DOWNCOUNTREG, -(s32)inst.constant); @@ -67,4 +71,73 @@ void RiscVJit::CompIR_Basic(IRInst inst) { } } +void RiscVJit::CompIR_Transfer(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::SetCtrlVFPU: + case IROp::SetCtrlVFPUReg: + case IROp::SetCtrlVFPUFReg: + case IROp::FpCondToReg: + case IROp::VfpuCtrlToReg: + case IROp::FMovFromGPR: + case IROp::FMovToGPR: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_System(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Interpret: + case IROp::Syscall: + case IROp::CallReplacement: + case IROp::Break: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_Breakpoint(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Breakpoint: + case IROp::MemoryCheck: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_ValidateAddress(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::ValidateAddress8: + case IROp::ValidateAddress16: + case IROp::ValidateAddress32: + case IROp::ValidateAddress128: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + } // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVCompVec.cpp b/Core/MIPS/RiscV/RiscVCompVec.cpp index 8ccc3f129a..ac54192dd9 100644 --- a/Core/MIPS/RiscV/RiscVCompVec.cpp +++ b/Core/MIPS/RiscV/RiscVCompVec.cpp @@ -34,4 +34,90 @@ namespace MIPSComp { using namespace RiscVGen; using namespace RiscVJitConstants; +void RiscVJit::CompIR_VecAssign(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Vec4Init: + case IROp::Vec4Shuffle: + case IROp::Vec4Mov: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_VecArith(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Vec4Add: + case IROp::Vec4Sub: + case IROp::Vec4Mul: + case IROp::Vec4Div: + case IROp::Vec4Scale: + case IROp::Vec4Neg: + case IROp::Vec4Abs: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_VecHoriz(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Vec4Dot: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_VecPack(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Vec2Unpack16To31: + case IROp::Vec2Unpack16To32: + case IROp::Vec4Unpack8To32: + case IROp::Vec4DuplicateUpperBitsAndShift1: + case IROp::Vec4Pack31To8: + case IROp::Vec4Pack32To8: + case IROp::Vec2Pack31To16: + case IROp::Vec2Pack32To16: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + +void RiscVJit::CompIR_VecClamp(IRInst inst) { + CONDITIONAL_DISABLE; + + switch (inst.op) { + case IROp::Vec4ClampToZero: + case IROp::Vec2ClampToZero: + CompIR_Generic(inst); + break; + + default: + INVALIDOP; + break; + } +} + } // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 7736188706..0be5b5d59d 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -112,6 +112,7 @@ void RiscVJit::CompileIRInst(IRInst inst) { break; case IROp::SetConst: + case IROp::SetConstF: case IROp::Downcount: case IROp::SetPC: case IROp::SetPCConst: @@ -139,24 +140,245 @@ void RiscVJit::CompileIRInst(IRInst inst) { case IROp::Mov: case IROp::Ext8to32: case IROp::Ext16to32: + CompIR_Assign(inst); + break; + case IROp::ReverseBits: case IROp::BSwap16: case IROp::BSwap32: - CompIR_Assign(inst); + case IROp::Clz: + CompIR_Bits(inst); + break; + + case IROp::Shl: + case IROp::Shr: + case IROp::Sar: + case IROp::Ror: + case IROp::ShlImm: + case IROp::ShrImm: + case IROp::SarImm: + case IROp::RorImm: + CompIR_Shift(inst); + break; + + case IROp::Slt: + case IROp::SltConst: + case IROp::SltU: + case IROp::SltUConst: + CompIR_Compare(inst); + break; + + case IROp::MovZ: + case IROp::MovNZ: + case IROp::Max: + case IROp::Min: + CompIR_CondAssign(inst); + break; + + case IROp::MtLo: + case IROp::MtHi: + case IROp::MfLo: + case IROp::MfHi: + CompIR_HiLo(inst); + break; + + case IROp::Mult: + case IROp::MultU: + case IROp::Madd: + case IROp::MaddU: + case IROp::Msub: + case IROp::MsubU: + CompIR_Mult(inst); + break; + + case IROp::Div: + case IROp::DivU: + CompIR_Div(inst); + break; + + case IROp::Load8: + case IROp::Load8Ext: + case IROp::Load16: + case IROp::Load16Ext: + case IROp::Load32: + CompIR_Load(inst); + break; + + case IROp::Load32Left: + case IROp::Load32Right: + CompIR_LoadShift(inst); + break; + + case IROp::LoadFloat: + CompIR_FLoad(inst); + break; + + case IROp::LoadVec4: + CompIR_VecLoad(inst); + break; + + case IROp::Store8: + case IROp::Store16: + case IROp::Store32: + CompIR_Store(inst); + break; + + case IROp::Store32Left: + case IROp::Store32Right: + CompIR_StoreShift(inst); + break; + + case IROp::StoreFloat: + CompIR_FStore(inst); + break; + + case IROp::StoreVec4: + CompIR_VecStore(inst); + break; + + case IROp::FAdd: + case IROp::FSub: + case IROp::FMul: + case IROp::FDiv: + case IROp::FSqrt: + case IROp::FNeg: + CompIR_FArith(inst); + break; + + case IROp::FMin: + case IROp::FMax: + CompIR_FCondAssign(inst); + break; + + case IROp::FMov: + case IROp::FAbs: + case IROp::FSign: + CompIR_FAssign(inst); + break; + + case IROp::FRound: + case IROp::FTrunc: + case IROp::FCeil: + case IROp::FFloor: + CompIR_FRound(inst); + break; + + case IROp::FCvtWS: + case IROp::FCvtSW: + CompIR_FCvt(inst); + break; + + case IROp::FSat0_1: + case IROp::FSatMinus1_1: + CompIR_FSat(inst); + break; + + case IROp::ZeroFpCond: + case IROp::FCmp: + case IROp::FCmovVfpuCC: + case IROp::FCmpVfpuBit: + case IROp::FCmpVfpuAggregate: + CompIR_FCompare(inst); + break; + + case IROp::RestoreRoundingMode: + case IROp::ApplyRoundingMode: + case IROp::UpdateRoundingMode: + CompIR_RoundingMode(inst); + break; + + case IROp::SetCtrlVFPU: + case IROp::SetCtrlVFPUReg: + case IROp::SetCtrlVFPUFReg: + case IROp::FpCondToReg: + case IROp::VfpuCtrlToReg: + case IROp::FMovFromGPR: + case IROp::FMovToGPR: + CompIR_Transfer(inst); + break; + + case IROp::Vec4Init: + case IROp::Vec4Shuffle: + case IROp::Vec4Mov: + CompIR_VecAssign(inst); + break; + + case IROp::Vec4Add: + case IROp::Vec4Sub: + case IROp::Vec4Mul: + case IROp::Vec4Div: + case IROp::Vec4Scale: + case IROp::Vec4Neg: + case IROp::Vec4Abs: + CompIR_VecArith(inst); + break; + + case IROp::Vec4Dot: + CompIR_VecHoriz(inst); + break; + + case IROp::Vec2Unpack16To31: + case IROp::Vec2Unpack16To32: + case IROp::Vec4Unpack8To32: + case IROp::Vec4DuplicateUpperBitsAndShift1: + case IROp::Vec4Pack31To8: + case IROp::Vec4Pack32To8: + case IROp::Vec2Pack31To16: + case IROp::Vec2Pack32To16: + CompIR_VecPack(inst); + break; + + case IROp::Vec4ClampToZero: + case IROp::Vec2ClampToZero: + CompIR_VecClamp(inst); + break; + + case IROp::FSin: + case IROp::FCos: + case IROp::FRSqrt: + case IROp::FRecip: + case IROp::FAsin: + CompIR_FSpecial(inst); + break; + + case IROp::Interpret: + case IROp::Syscall: + case IROp::CallReplacement: + case IROp::Break: + CompIR_System(inst); + break; + + case IROp::Breakpoint: + case IROp::MemoryCheck: + CompIR_Breakpoint(inst); + break; + + case IROp::ValidateAddress8: + case IROp::ValidateAddress16: + case IROp::ValidateAddress32: + case IROp::ValidateAddress128: + CompIR_ValidateAddress(inst); break; case IROp::ExitToConst: case IROp::ExitToReg: - case IROp::ExitToConstIfEq: - case IROp::ExitToConstIfNeq: - case IROp::ExitToConstIfGeZ: - case IROp::ExitToConstIfLtZ: - case IROp::ExitToConstIfLeZ: case IROp::ExitToPC: CompIR_Exit(inst); break; + case IROp::ExitToConstIfEq: + case IROp::ExitToConstIfNeq: + case IROp::ExitToConstIfGtZ: + case IROp::ExitToConstIfGeZ: + case IROp::ExitToConstIfLtZ: + case IROp::ExitToConstIfLeZ: + case IROp::ExitToConstIfFpTrue: + case IROp::ExitToConstIfFpFalse: + CompIR_ExitIf(inst); + break; + default: + _assert_msg_(false, "Unexpected IR op %d", (int)inst.op); CompIR_Generic(inst); break; } diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index 1467d80b3e..d828d2ee56 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -63,12 +63,46 @@ private: void FlushAll(); - void CompIR_Generic(IRInst inst); - void CompIR_Basic(IRInst inst); void CompIR_Arith(IRInst inst); - void CompIR_Logic(IRInst inst); void CompIR_Assign(IRInst inst); + void CompIR_Basic(IRInst inst); + void CompIR_Bits(IRInst inst); + void CompIR_Breakpoint(IRInst inst); + void CompIR_Compare(IRInst inst); + void CompIR_CondAssign(IRInst inst); + void CompIR_Div(IRInst inst); void CompIR_Exit(IRInst inst); + void CompIR_ExitIf(IRInst inst); + void CompIR_FArith(IRInst inst); + void CompIR_FAssign(IRInst inst); + void CompIR_FCompare(IRInst inst); + void CompIR_FCondAssign(IRInst inst); + void CompIR_FCvt(IRInst inst); + void CompIR_FLoad(IRInst inst); + void CompIR_FRound(IRInst inst); + void CompIR_FSat(IRInst inst); + void CompIR_FSpecial(IRInst inst); + void CompIR_FStore(IRInst inst); + void CompIR_Generic(IRInst inst); + void CompIR_HiLo(IRInst inst); + void CompIR_Load(IRInst inst); + void CompIR_LoadShift(IRInst inst); + void CompIR_Logic(IRInst inst); + void CompIR_Mult(IRInst inst); + void CompIR_RoundingMode(IRInst inst); + void CompIR_Shift(IRInst inst); + void CompIR_Store(IRInst inst); + void CompIR_StoreShift(IRInst inst); + void CompIR_System(IRInst inst); + void CompIR_Transfer(IRInst inst); + void CompIR_VecArith(IRInst inst); + void CompIR_VecAssign(IRInst inst); + void CompIR_VecClamp(IRInst inst); + void CompIR_VecHoriz(IRInst inst); + void CompIR_VecLoad(IRInst inst); + void CompIR_VecPack(IRInst inst); + void CompIR_VecStore(IRInst inst); + void CompIR_ValidateAddress(IRInst inst); RiscVRegCache gpr; From 165169eb3126757c2156a0134dfd0424ecac7d7c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 21 Jul 2023 20:32:47 -0700 Subject: [PATCH 072/167] riscv: Implement load and store ops. --- Core/MIPS/RiscV/RiscVCompLoadStore.cpp | 99 +++++++++++++++++++++++++- Core/MIPS/RiscV/RiscVJit.h | 4 ++ Core/MIPS/RiscV/RiscVRegCache.cpp | 10 +++ 3 files changed, 111 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp index 13c9437fa4..34cbd3f131 100644 --- a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp +++ b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp @@ -34,16 +34,82 @@ namespace MIPSComp { using namespace RiscVGen; using namespace RiscVJitConstants; +void RiscVJit::SetScratch1ToSrc1Address(IRReg src1) { + gpr.MapReg(src1); +#ifdef MASKED_PSP_MEMORY + SLLIW(SCRATCH1, gpr.R(src1), 2); + SRLIW(SCRATCH1, SCRATCH1, 2); + ADD(SCRATCH1, SCRATCH1, MEMBASEREG); +#else + // Clear the top bits to be safe. + if (cpu_info.RiscV_Zba) { + ADD_UW(SCRATCH1, gpr.R(src1), MEMBASEREG); + } else { + _assert_(XLEN == 64); + SLLI(SCRATCH1, gpr.R(src1), 32); + SRLI(SCRATCH1, SCRATCH1, 32); + ADD(SCRATCH1, SCRATCH1, MEMBASEREG); + } +#endif +} + +int32_t RiscVJit::AdjustForAddressOffset(RiscVGen::RiscVReg *reg, int32_t constant) { + if (constant < -2048 || constant > 2047) { + LI(SCRATCH2, constant); + ADD(SCRATCH1, *reg, SCRATCH2); + *reg = SCRATCH1; + return 0; + } + return constant; +} + void RiscVJit::CompIR_Load(IRInst inst) { CONDITIONAL_DISABLE; + gpr.SpillLock(inst.dest, inst.src1); + RiscVReg addrReg = INVALID_REG; + if (inst.src1 == MIPS_REG_ZERO) { + // This will get changed by AdjustForAddressOffset. + addrReg = MEMBASEREG; +#ifdef MASKED_PSP_MEMORY + inst.constant &= Memory::MEMVIEW32_MASK; +#endif + } else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) { + addrReg = gpr.MapRegAsPointer(inst.src1); + } else { + SetScratch1ToSrc1Address(inst.src1); + addrReg = SCRATCH1; + } + // If they're the same, MapReg may subtract MEMBASEREG, so just mark dirty. + if (inst.dest == inst.src1) + gpr.MarkDirty(gpr.R(inst.dest)); + else + gpr.MapReg(inst.dest, MAP_NOINIT); + gpr.ReleaseSpillLock(inst.dest, inst.src1); + + s32 imm = AdjustForAddressOffset(&addrReg, inst.constant); + + // TODO: Safe memory? Or enough to have crash handler + validate? + switch (inst.op) { case IROp::Load8: + LBU(gpr.R(inst.dest), addrReg, imm); + break; + case IROp::Load8Ext: + LB(gpr.R(inst.dest), addrReg, imm); + break; + case IROp::Load16: + LHU(gpr.R(inst.dest), addrReg, imm); + break; + case IROp::Load16Ext: + LH(gpr.R(inst.dest), addrReg, imm); + break; + case IROp::Load32: - CompIR_Generic(inst); + LWU(gpr.R(inst.dest), addrReg, imm); break; default: @@ -98,11 +164,40 @@ void RiscVJit::CompIR_VecLoad(IRInst inst) { void RiscVJit::CompIR_Store(IRInst inst) { CONDITIONAL_DISABLE; + gpr.SpillLock(inst.src3, inst.src1); + RiscVReg addrReg = INVALID_REG; + if (inst.src1 == MIPS_REG_ZERO) { + // This will get changed by AdjustForAddressOffset. + addrReg = MEMBASEREG; +#ifdef MASKED_PSP_MEMORY + inst.constant &= Memory::MEMVIEW32_MASK; +#endif + } else if ((jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) && inst.src3 != inst.src1) { + addrReg = gpr.MapRegAsPointer(inst.src1); + } else { + SetScratch1ToSrc1Address(inst.src1); + addrReg = SCRATCH1; + } + RiscVReg valueReg = gpr.TryMapTempImm(inst.src3); + if (valueReg == INVALID_REG) + valueReg = gpr.MapReg(inst.src3); + gpr.ReleaseSpillLock(inst.src3, inst.src1); + + s32 imm = AdjustForAddressOffset(&addrReg, inst.constant); + + // TODO: Safe memory? Or enough to have crash handler + validate? + switch (inst.op) { case IROp::Store8: + SB(valueReg, addrReg, imm); + break; + case IROp::Store16: + SH(valueReg, addrReg, imm); + break; + case IROp::Store32: - CompIR_Generic(inst); + SW(valueReg, addrReg, imm); break; default: diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index d828d2ee56..022410e152 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -104,6 +104,10 @@ private: void CompIR_VecStore(IRInst inst); void CompIR_ValidateAddress(IRInst inst); + void SetScratch1ToSrc1Address(IRReg src1); + // Modifies SCRATCH regs. + int32_t AdjustForAddressOffset(RiscVGen::RiscVReg *reg, int32_t constant); + RiscVRegCache gpr; const u8 *enterDispatcher_ = nullptr; diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index b209141cb7..52c3b90d23 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -169,6 +169,16 @@ void RiscVRegCache::MarkDirty(RiscVReg reg) { // Can't mark X0 dirty. _dbg_assert_(reg > X0 && reg <= X31); ar[reg].isDirty = true; + // If reg is written to, pointerification is lost. + ar[reg].pointerified = false; + if (ar[reg].mipsReg != IRREG_INVALID) { + RegStatusMIPS &m = mr[ar[reg].mipsReg]; + if (m.loc == MIPSLoc::RVREG_AS_PTR || m.loc == MIPSLoc::RVREG_IMM) { + m.loc = MIPSLoc::RVREG; + m.imm = -1; + } + _dbg_assert_(m.loc == MIPSLoc::RVREG); + } } void RiscVRegCache::SetRegImm(RiscVReg reg, u64 imm) { From 1dfedde7414f6b5b842d1e4489b8096f9caceb56 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 21 Jul 2023 23:09:06 -0700 Subject: [PATCH 073/167] riscv: Avoid needless save/load around compile. --- Core/MIPS/RiscV/RiscVAsm.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVAsm.cpp b/Core/MIPS/RiscV/RiscVAsm.cpp index 77ab1238db..ebd043233c 100644 --- a/Core/MIPS/RiscV/RiscVAsm.cpp +++ b/Core/MIPS/RiscV/RiscVAsm.cpp @@ -209,12 +209,10 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { JR(SCRATCH1); SetJumpTarget(skipJump); - // No block found, let's jit. Might be able to optimize reg/frm saving later. - SaveStaticRegisters(); + // No block found, let's jit. We don't need to save static regs, they're all callee saved. RestoreRoundingMode(true); QuickCallFunction(SCRATCH1, &MIPSComp::JitAt); ApplyRoundingMode(true); - LoadStaticRegisters(); // Try again, the block index should be set now. J(dispatcherNoCheck_); From 624caa2dea66d3c80e57ba950cc0b099264c89f4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 21 Jul 2023 23:09:34 -0700 Subject: [PATCH 074/167] riscv: Implement the simplest exits. --- Core/MIPS/RiscV/RiscVCompBranch.cpp | 32 ++++++++++++++++++++++++++--- Core/MIPS/RiscV/RiscVJit.h | 1 + 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompBranch.cpp b/Core/MIPS/RiscV/RiscVCompBranch.cpp index a14ee49d0c..0532ae389b 100644 --- a/Core/MIPS/RiscV/RiscVCompBranch.cpp +++ b/Core/MIPS/RiscV/RiscVCompBranch.cpp @@ -37,13 +37,39 @@ using namespace RiscVJitConstants; void RiscVJit::CompIR_Exit(IRInst inst) { CONDITIONAL_DISABLE; - // Will probably always want this? - FlushAll(); + RiscVReg exitReg = INVALID_REG; switch (inst.op) { case IROp::ExitToConst: + FlushAll(); + LI(SCRATCH1, inst.constant); + if (JInRange(dispatcherPCInSCRATCH1_)) { + J(dispatcherPCInSCRATCH1_); + } else { + LI(SCRATCH2, dispatcherPCInSCRATCH1_); + JALR(R_ZERO, SCRATCH2, 0); + } + break; + case IROp::ExitToReg: + exitReg = gpr.MapReg(inst.src1); + FlushAll(); + MV(SCRATCH1, exitReg); + if (JInRange(dispatcherPCInSCRATCH1_)) { + J(dispatcherPCInSCRATCH1_); + } else { + LI(SCRATCH2, dispatcherPCInSCRATCH1_); + JALR(R_ZERO, SCRATCH2, 0); + } + break; + case IROp::ExitToPC: - CompIR_Generic(inst); + FlushAll(); + if (JInRange(dispatcher_)) { + J(dispatcher_); + } else { + LI(SCRATCH2, dispatcher_); + JALR(R_ZERO, SCRATCH2, 0); + } break; default: diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index 022410e152..f50d1b6782 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -61,6 +61,7 @@ private: void SaveStaticRegisters(); void LoadStaticRegisters(); + // Note: destroys SCRATCH1. void FlushAll(); void CompIR_Arith(IRInst inst); From d31eded9ba371ed4656aff225ef5160dae4a326e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 21 Jul 2023 23:38:20 -0700 Subject: [PATCH 075/167] riscv: Allow dirty pointers, explicitly. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 2 +- Core/MIPS/RiscV/RiscVRegCache.cpp | 11 +++++++++++ Core/MIPS/RiscV/RiscVRegCache.h | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index 2e4a7a248b..fb9e6e2269 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -66,7 +66,7 @@ void RiscVJit::CompIR_Arith(IRInst inst) { if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) { // Typical of stack pointer updates. if (gpr.IsMappedAsPointer(inst.src1) && inst.dest == inst.src1 && allowPtrMath) { - gpr.MarkDirty(gpr.RPtr(inst.dest)); + gpr.MarkPtrDirty(gpr.RPtr(inst.dest)); ADDI(gpr.RPtr(inst.dest), gpr.RPtr(inst.dest), inst.constant); } else { gpr.MapDirtyIn(inst.dest, inst.src1); diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index 52c3b90d23..1918ed5ab3 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -181,6 +181,17 @@ void RiscVRegCache::MarkDirty(RiscVReg reg) { } } +void RiscVRegCache::MarkPtrDirty(RiscVReg reg) { + // Can't mark X0 dirty. + _dbg_assert_(reg > X0 && reg <= X31); + ar[reg].isDirty = true; + if (ar[reg].mipsReg != IRREG_INVALID) { + _dbg_assert_(mr[ar[reg].mipsReg].loc == MIPSLoc::RVREG_AS_PTR); + } else { + _dbg_assert_(ar[reg].pointerified); + } +} + void RiscVRegCache::SetRegImm(RiscVReg reg, u64 imm) { _dbg_assert_(reg != R_ZERO || imm == 0); _dbg_assert_(reg >= X0 && reg <= X31); diff --git a/Core/MIPS/RiscV/RiscVRegCache.h b/Core/MIPS/RiscV/RiscVRegCache.h index 326332d620..96a8272aef 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.h +++ b/Core/MIPS/RiscV/RiscVRegCache.h @@ -118,6 +118,7 @@ public: bool IsInRAM(IRRegIndex reg); void MarkDirty(RiscVGen::RiscVReg reg); + void MarkPtrDirty(RiscVGen::RiscVReg reg); void MapIn(IRRegIndex rs); void MapInIn(IRRegIndex rd, IRRegIndex rs); void MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad = true); From 76e3246065fd8a95227fc79ee200520ba3923aa7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 22 Jul 2023 00:30:29 -0700 Subject: [PATCH 076/167] riscv: Reduce jit codesize a bit. --- Common/RiscVEmitter.cpp | 15 +++++++++------ Common/RiscVEmitter.h | 8 +++++++- Core/MIPS/RiscV/RiscVCompBranch.cpp | 21 +++------------------ Core/MIPS/RiscV/RiscVJit.cpp | 7 +++---- 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/Common/RiscVEmitter.cpp b/Common/RiscVEmitter.cpp index f557354fff..0029da5287 100644 --- a/Common/RiscVEmitter.cpp +++ b/Common/RiscVEmitter.cpp @@ -1180,13 +1180,16 @@ bool RiscVEmitter::CJInRange(const void *src, const void *dst) const { return BJInRange(src, dst, 12); } -void RiscVEmitter::QuickCallFunction(RiscVReg scratchreg, const u8 *func) { - if (!JInRange(GetCodePointer(), func)) { - // TODO: Might be able to optimize a tiny bit by taking advantage of simm12. - LI(scratchreg, (uintptr_t)func); - JALR(R_RA, scratchreg, 0); +void RiscVEmitter::QuickJAL(RiscVReg scratchreg, RiscVReg rd, const u8 *dst) { + if (!JInRange(GetCodePointer(), dst)) { + static_assert(sizeof(intptr_t) <= sizeof(int64_t)); + int64_t pcdelta = (int64_t)dst - (int64_t)GetCodePointer(); + int32_t lower = (int32_t)SignReduce64(pcdelta, 12); + uintptr_t upper = ((pcdelta - lower) >> 12) << 12; + LI(scratchreg, (uintptr_t)GetCodePointer() + upper); + JALR(rd, scratchreg, lower); } else { - JAL(R_RA, func); + JAL(rd, dst); } } diff --git a/Common/RiscVEmitter.h b/Common/RiscVEmitter.h index fd093d2509..439a313fe9 100644 --- a/Common/RiscVEmitter.h +++ b/Common/RiscVEmitter.h @@ -213,7 +213,13 @@ public: bool BInRange(const void *func) const; bool JInRange(const void *func) const; - void QuickCallFunction(RiscVReg scratchreg, const u8 *func); + void QuickJAL(RiscVReg scratchreg, RiscVReg rd, const u8 *dst); + void QuickJ(RiscVReg scratchreg, const u8 *dst) { + QuickJAL(scratchreg, R_ZERO, dst); + } + void QuickCallFunction(RiscVReg scratchreg, const u8 *func) { + QuickJAL(scratchreg, R_RA, func); + } template void QuickCallFunction(RiscVReg scratchreg, T *func) { static_assert(std::is_function::value, "QuickCallFunction without function"); diff --git a/Core/MIPS/RiscV/RiscVCompBranch.cpp b/Core/MIPS/RiscV/RiscVCompBranch.cpp index 0532ae389b..f2d481426d 100644 --- a/Core/MIPS/RiscV/RiscVCompBranch.cpp +++ b/Core/MIPS/RiscV/RiscVCompBranch.cpp @@ -42,34 +42,19 @@ void RiscVJit::CompIR_Exit(IRInst inst) { case IROp::ExitToConst: FlushAll(); LI(SCRATCH1, inst.constant); - if (JInRange(dispatcherPCInSCRATCH1_)) { - J(dispatcherPCInSCRATCH1_); - } else { - LI(SCRATCH2, dispatcherPCInSCRATCH1_); - JALR(R_ZERO, SCRATCH2, 0); - } + QuickJ(SCRATCH2, dispatcherPCInSCRATCH1_); break; case IROp::ExitToReg: exitReg = gpr.MapReg(inst.src1); FlushAll(); MV(SCRATCH1, exitReg); - if (JInRange(dispatcherPCInSCRATCH1_)) { - J(dispatcherPCInSCRATCH1_); - } else { - LI(SCRATCH2, dispatcherPCInSCRATCH1_); - JALR(R_ZERO, SCRATCH2, 0); - } + QuickJ(SCRATCH2, dispatcherPCInSCRATCH1_); break; case IROp::ExitToPC: FlushAll(); - if (JInRange(dispatcher_)) { - J(dispatcher_); - } else { - LI(SCRATCH2, dispatcher_); - JALR(R_ZERO, SCRATCH2, 0); - } + QuickJ(SCRATCH2, dispatcher_); break; default: diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 0be5b5d59d..57d53242d7 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -35,6 +35,7 @@ RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo) } AllocCodeSpace(1024 * 1024 * 16); + SetAutoCompress(true); // TODO: Consider replacing block num method form IRJit - this is 2MB. blockStartAddrs_ = new const u8 *[MAX_ALLOWED_JIT_BLOCKS]; @@ -91,8 +92,7 @@ bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u // Note: a properly constructed block should never get here. // TODO: Need to do more than just this? Call a func to set an exception? - LI(SCRATCH2, crashHandler_); - JALR(R_ZERO, SCRATCH2, 0); + QuickJ(SCRATCH2, crashHandler_); FlushIcache(); @@ -401,8 +401,7 @@ void RiscVJit::CompIR_Generic(IRInst inst) { BNE(X10, R_ZERO, dispatcherPCInSCRATCH1_); } else { FixupBranch skip = BEQ(X10, R_ZERO); - LI(SCRATCH2, dispatcherPCInSCRATCH1_); - JALR(R_ZERO, SCRATCH2, 0); + QuickJ(SCRATCH2, dispatcherPCInSCRATCH1_); SetJumpTarget(skip); } } From 720f868a1083958217f64d40d8af81900ecfb71e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 22 Jul 2023 13:47:44 -0700 Subject: [PATCH 077/167] riscv: Use R_RA as a temporary for calls. This is the most logical thing, since we're about to write it anyway. --- Common/RiscVEmitter.h | 8 ++++---- Core/MIPS/RiscV/RiscVAsm.cpp | 6 +++--- Core/MIPS/RiscV/RiscVCompBranch.cpp | 6 +++--- Core/MIPS/RiscV/RiscVJit.cpp | 12 ++++++------ Core/MIPS/RiscV/RiscVRegCache.cpp | 5 ++--- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/Common/RiscVEmitter.h b/Common/RiscVEmitter.h index 439a313fe9..fedb0b0c7f 100644 --- a/Common/RiscVEmitter.h +++ b/Common/RiscVEmitter.h @@ -217,13 +217,13 @@ public: void QuickJ(RiscVReg scratchreg, const u8 *dst) { QuickJAL(scratchreg, R_ZERO, dst); } - void QuickCallFunction(RiscVReg scratchreg, const u8 *func) { - QuickJAL(scratchreg, R_RA, func); + void QuickCallFunction(const u8 *func) { + QuickJAL(R_RA, R_RA, func); } template - void QuickCallFunction(RiscVReg scratchreg, T *func) { + void QuickCallFunction(T *func) { static_assert(std::is_function::value, "QuickCallFunction without function"); - QuickCallFunction(scratchreg, (const u8 *)func); + QuickCallFunction((const u8 *)func); } void LUI(RiscVReg rd, s32 simm32); diff --git a/Core/MIPS/RiscV/RiscVAsm.cpp b/Core/MIPS/RiscV/RiscVAsm.cpp index ebd043233c..afdbeb75db 100644 --- a/Core/MIPS/RiscV/RiscVAsm.cpp +++ b/Core/MIPS/RiscV/RiscVAsm.cpp @@ -156,7 +156,7 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { // Advance can change the downcount (or thread), so must save/restore around it. SaveStaticRegisters(); RestoreRoundingMode(true); - QuickCallFunction(SCRATCH1, &CoreTiming::Advance); + QuickCallFunction(&CoreTiming::Advance); ApplyRoundingMode(true); LoadStaticRegisters(); @@ -183,7 +183,7 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { MV(X10, DOWNCOUNTREG); MV(X11, MEMBASEREG); MV(X12, JITBASEREG); - QuickCallFunction(X13, &ShowPC); + QuickCallFunction(&ShowPC); } LWU(SCRATCH1, CTXREG, offsetof(MIPSState, pc)); @@ -211,7 +211,7 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { // No block found, let's jit. We don't need to save static regs, they're all callee saved. RestoreRoundingMode(true); - QuickCallFunction(SCRATCH1, &MIPSComp::JitAt); + QuickCallFunction(&MIPSComp::JitAt); ApplyRoundingMode(true); // Try again, the block index should be set now. diff --git a/Core/MIPS/RiscV/RiscVCompBranch.cpp b/Core/MIPS/RiscV/RiscVCompBranch.cpp index f2d481426d..ebd4f6bf59 100644 --- a/Core/MIPS/RiscV/RiscVCompBranch.cpp +++ b/Core/MIPS/RiscV/RiscVCompBranch.cpp @@ -42,19 +42,19 @@ void RiscVJit::CompIR_Exit(IRInst inst) { case IROp::ExitToConst: FlushAll(); LI(SCRATCH1, inst.constant); - QuickJ(SCRATCH2, dispatcherPCInSCRATCH1_); + QuickJ(R_RA, dispatcherPCInSCRATCH1_); break; case IROp::ExitToReg: exitReg = gpr.MapReg(inst.src1); FlushAll(); MV(SCRATCH1, exitReg); - QuickJ(SCRATCH2, dispatcherPCInSCRATCH1_); + QuickJ(R_RA, dispatcherPCInSCRATCH1_); break; case IROp::ExitToPC: FlushAll(); - QuickJ(SCRATCH2, dispatcher_); + QuickJ(R_RA, dispatcher_); break; default: diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 57d53242d7..054b80808e 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -92,7 +92,7 @@ bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u // Note: a properly constructed block should never get here. // TODO: Need to do more than just this? Call a func to set an exception? - QuickJ(SCRATCH2, crashHandler_); + QuickJ(R_RA, crashHandler_); FlushIcache(); @@ -393,7 +393,7 @@ void RiscVJit::CompIR_Generic(IRInst inst) { FlushAll(); LI(X10, value, SCRATCH2); SaveStaticRegisters(); - QuickCallFunction(SCRATCH2, &DoIRInst); + QuickCallFunction(&DoIRInst); LoadStaticRegisters(); // Result in X10 aka SCRATCH1. _assert_(X10 == SCRATCH1); @@ -401,7 +401,7 @@ void RiscVJit::CompIR_Generic(IRInst inst) { BNE(X10, R_ZERO, dispatcherPCInSCRATCH1_); } else { FixupBranch skip = BEQ(X10, R_ZERO); - QuickJ(SCRATCH2, dispatcherPCInSCRATCH1_); + QuickJ(R_RA, dispatcherPCInSCRATCH1_); SetJumpTarget(skip); } } @@ -497,7 +497,7 @@ void RiscVJit::RestoreRoundingMode(bool force) { void RiscVJit::ApplyRoundingMode(bool force) { // TODO: Also could maybe sometimes skip? - //QuickCallFunction(SCRATCH2, applyRoundingMode_); + //QuickCallFunction(applyRoundingMode_); } void RiscVJit::MovFromPC(RiscVGen::RiscVReg r) { @@ -510,7 +510,7 @@ void RiscVJit::MovToPC(RiscVGen::RiscVReg r) { void RiscVJit::SaveStaticRegisters() { if (jo.useStaticAlloc) { - QuickCallFunction(SCRATCH2, saveStaticRegisters_); + QuickCallFunction(saveStaticRegisters_); } else { // Inline the single operation SW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); @@ -519,7 +519,7 @@ void RiscVJit::SaveStaticRegisters() { void RiscVJit::LoadStaticRegisters() { if (jo.useStaticAlloc) { - QuickCallFunction(SCRATCH2, loadStaticRegisters_); + QuickCallFunction(loadStaticRegisters_); } else { LW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount)); } diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index 1918ed5ab3..cce041d9f1 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -72,10 +72,10 @@ const RiscVReg *RiscVRegCache::GetMIPSAllocationOrder(int &count) { // X8 and X9 are the most ideal for static alloc because they can be used with compression. // Otherwise we stick to saved regs - might not be necessary. static const RiscVReg allocationOrder[] = { - X7, X8, X9, X12, X13, X14, X1, X5, X6, X15, X16, X17, X18, X19, X20, X21, X22, X23, X28, X29, X30, X31, + X7, X8, X9, X12, X13, X14, X5, X6, X15, X16, X17, X18, X19, X20, X21, X22, X23, X28, X29, X30, X31, }; static const RiscVReg allocationOrderStaticAlloc[] = { - X7, X12, X13, X14, X1, X5, X6, X15, X16, X17, X21, X22, X23, X28, X29, X30, X31, + X7, X12, X13, X14, X5, X6, X15, X16, X17, X21, X22, X23, X28, X29, X30, X31, }; if (jo_->useStaticAlloc) { @@ -123,7 +123,6 @@ void RiscVRegCache::EmitSaveStaticRegisters() { // This only needs to run once (by Asm) so checks don't need to be fast. for (int i = 0; i < count; i++) { int offset = GetMipsRegOffset(allocs[i].mr); - // TODO: Check dirty? emit_->SW(allocs[i].ar, CTXREG, offset); } } From 8ee73264bfe9a77d96d8c19cfa2575c193fa5520 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 22 Jul 2023 14:12:32 -0700 Subject: [PATCH 078/167] riscv: Correct depointerify on FlushAll(). --- Core/MIPS/ARM64/Arm64RegCache.cpp | 2 +- Core/MIPS/RiscV/RiscVRegCache.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index ac97e33e74..3bfede4dbb 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -777,7 +777,7 @@ void Arm64RegCache::FlushAll() { // Re-pointerify emit_->MOVK(EncodeRegTo64(allocs[i].ar), ((uint64_t)Memory::base) >> 32, SHIFT_32); ar[allocs[i].ar].pointerified = true; - } else { + } else if (!allocs[i].pointerified) { // If this register got pointerified on the way, mark it as not, so that after save/reload (like in an interpreter fallback), it won't be regarded as such, as it simply won't be. ar[allocs[i].ar].pointerified = false; } diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index cce041d9f1..ff88d5c2c7 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -795,9 +795,10 @@ void RiscVRegCache::FlushAll() { for (int i = 0; i < count; i++) { if (allocs[i].pointerified && !ar[allocs[i].ar].pointerified && jo_->enablePointerify) { // Re-pointerify + _dbg_assert_(mr[allocs[i].mr].loc == MIPSLoc::RVREG); AddMemBase(allocs[i].ar); ar[allocs[i].ar].pointerified = true; - } else { + } else if (!allocs[i].pointerified) { // If this register got pointerified on the way, mark it as not. // This is so that after save/reload (like in an interpreter fallback), // it won't be regarded as such, as it may no longer be. From 7aafa11d2444f0b47e5e964ae7de6d1e9df44cd6 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 22 Jul 2023 14:29:38 -0700 Subject: [PATCH 079/167] riscv: Implement conditional exits. --- Core/MIPS/RiscV/RiscVCompBranch.cpp | 83 ++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompBranch.cpp b/Core/MIPS/RiscV/RiscVCompBranch.cpp index ebd4f6bf59..8d455986d2 100644 --- a/Core/MIPS/RiscV/RiscVCompBranch.cpp +++ b/Core/MIPS/RiscV/RiscVCompBranch.cpp @@ -48,6 +48,7 @@ void RiscVJit::CompIR_Exit(IRInst inst) { case IROp::ExitToReg: exitReg = gpr.MapReg(inst.src1); FlushAll(); + // TODO: If ever we don't read this back in dispatcherPCInSCRATCH1_, we should zero upper. MV(SCRATCH1, exitReg); QuickJ(R_RA, dispatcherPCInSCRATCH1_); break; @@ -66,15 +67,93 @@ void RiscVJit::CompIR_Exit(IRInst inst) { void RiscVJit::CompIR_ExitIf(IRInst inst) { CONDITIONAL_DISABLE; - // Will probably always want this? - FlushAll(); + RiscVReg lhs = INVALID_REG; + RiscVReg rhs = INVALID_REG; + FixupBranch fixup; switch (inst.op) { case IROp::ExitToConstIfEq: case IROp::ExitToConstIfNeq: + gpr.MapInIn(inst.src1, inst.src2); + lhs = gpr.R(inst.src1); + rhs = gpr.R(inst.src2); + FlushAll(); + + // For proper compare, we must sign extend so they both match or don't match. + // But don't change pointers, in case one is SP (happens in LittleBigPlanet.) + if (XLEN >= 64 && gpr.IsMappedAsPointer(inst.src1)) { + ADDIW(SCRATCH1, lhs, 0); + lhs = SCRATCH1; + } else if (XLEN >= 64 && lhs != R_ZERO) { + ADDIW(lhs, lhs, 0); + } + if (XLEN >= 64 && gpr.IsMappedAsPointer(inst.src2)) { + ADDIW(SCRATCH2, rhs, 0); + rhs = SCRATCH2; + } else if (XLEN >= 64 && rhs != R_ZERO) { + ADDIW(rhs, rhs, 0); + } + + switch (inst.op) { + case IROp::ExitToConstIfEq: + fixup = BNE(lhs, rhs); + break; + + case IROp::ExitToConstIfNeq: + fixup = BEQ(lhs, rhs); + break; + + default: + INVALIDOP; + break; + } + + LI(SCRATCH1, inst.constant); + QuickJ(R_RA, dispatcherPCInSCRATCH1_); + SetJumpTarget(fixup); + break; + case IROp::ExitToConstIfGtZ: case IROp::ExitToConstIfGeZ: case IROp::ExitToConstIfLtZ: case IROp::ExitToConstIfLeZ: + lhs = gpr.MapReg(inst.src1); + FlushAll(); + + // For proper compare, we must sign extend. + if (XLEN >= 64 && gpr.IsMappedAsPointer(inst.src1)) { + ADDIW(SCRATCH1, lhs, 0); + lhs = SCRATCH1; + } else if (XLEN >= 64 && lhs != R_ZERO) { + ADDIW(lhs, lhs, 0); + } + + switch (inst.op) { + case IROp::ExitToConstIfGtZ: + fixup = BGE(R_ZERO, lhs); + break; + + case IROp::ExitToConstIfGeZ: + fixup = BLT(lhs, R_ZERO); + break; + + case IROp::ExitToConstIfLtZ: + fixup = BGE(lhs, R_ZERO); + break; + + case IROp::ExitToConstIfLeZ: + fixup = BLT(R_ZERO, lhs); + break; + + default: + INVALIDOP; + break; + } + + LI(SCRATCH1, inst.constant); + QuickJ(R_RA, dispatcherPCInSCRATCH1_); + SetJumpTarget(fixup); + break; + case IROp::ExitToConstIfFpTrue: case IROp::ExitToConstIfFpFalse: CompIR_Generic(inst); From 94be343591abacf1ae16d78adc06b1f01ab97d21 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 22 Jul 2023 18:32:43 -0700 Subject: [PATCH 080/167] riscv: Try to keep regs normalized, track. Since we can't address the lower 32-bits only in compares, this can help us avoid renormalizing before a compare. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 24 ++-- Core/MIPS/RiscV/RiscVCompBranch.cpp | 34 ++--- Core/MIPS/RiscV/RiscVCompLoadStore.cpp | 6 +- Core/MIPS/RiscV/RiscVRegCache.cpp | 165 ++++++++++++++++++++----- Core/MIPS/RiscV/RiscVRegCache.h | 41 ++++-- 5 files changed, 196 insertions(+), 74 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index fb9e6e2269..b9eb8c2df0 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -53,12 +53,12 @@ void RiscVJit::CompIR_Arith(IRInst inst) { switch (inst.op) { case IROp::Add: - gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32); ADDW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); break; case IROp::Sub: - gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32); SUBW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); break; @@ -69,24 +69,24 @@ void RiscVJit::CompIR_Arith(IRInst inst) { gpr.MarkPtrDirty(gpr.RPtr(inst.dest)); ADDI(gpr.RPtr(inst.dest), gpr.RPtr(inst.dest), inst.constant); } else { - gpr.MapDirtyIn(inst.dest, inst.src1); + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); ADDIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant); } } else { - gpr.MapDirtyIn(inst.dest, inst.src1); + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); LI(SCRATCH1, (s32)inst.constant, SCRATCH2); ADDW(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1); } break; case IROp::SubConst: - gpr.MapDirtyIn(inst.dest, inst.src1); + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); LI(SCRATCH1, (s32)inst.constant, SCRATCH2); SUBW(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1); break; case IROp::Neg: - gpr.MapDirtyIn(inst.dest, inst.src1); + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); SUBW(gpr.R(inst.dest), R_ZERO, gpr.R(inst.src1)); break; @@ -123,14 +123,15 @@ void RiscVJit::CompIR_Assign(IRInst inst) { case IROp::Mov: gpr.MapDirtyIn(inst.dest, inst.src1); MV(gpr.R(inst.dest), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1)); break; case IROp::Ext8to32: if (cpu_info.RiscV_Zbb) { - gpr.MapDirtyIn(inst.dest, inst.src1); + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); SEXT_B(gpr.R(inst.dest), gpr.R(inst.src1)); } else { - gpr.MapDirtyIn(inst.dest, inst.src1); + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); SLLI(gpr.R(inst.dest), gpr.R(inst.src1), 24); SRAIW(gpr.R(inst.dest), gpr.R(inst.dest), 24); } @@ -138,10 +139,10 @@ void RiscVJit::CompIR_Assign(IRInst inst) { case IROp::Ext16to32: if (cpu_info.RiscV_Zbb) { - gpr.MapDirtyIn(inst.dest, inst.src1); + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); SEXT_H(gpr.R(inst.dest), gpr.R(inst.src1)); } else { - gpr.MapDirtyIn(inst.dest, inst.src1); + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); SLLI(gpr.R(inst.dest), gpr.R(inst.src1), 16); SRAIW(gpr.R(inst.dest), gpr.R(inst.dest), 16); } @@ -171,7 +172,8 @@ void RiscVJit::CompIR_Bits(IRInst inst) { REV8(gpr.R(inst.dest), gpr.R(inst.src1)); if (XLEN >= 64) { // REV8 swaps the entire register, so get the 32 highest bits. - SRLI(gpr.R(inst.dest), gpr.R(inst.dest), XLEN - 32); + SRAI(gpr.R(inst.dest), gpr.R(inst.dest), XLEN - 32); + gpr.MarkDirty(gpr.R(inst.dest), true); } } else { CompIR_Generic(inst); diff --git a/Core/MIPS/RiscV/RiscVCompBranch.cpp b/Core/MIPS/RiscV/RiscVCompBranch.cpp index 8d455986d2..b3e4247df5 100644 --- a/Core/MIPS/RiscV/RiscVCompBranch.cpp +++ b/Core/MIPS/RiscV/RiscVCompBranch.cpp @@ -76,23 +76,23 @@ void RiscVJit::CompIR_ExitIf(IRInst inst) { gpr.MapInIn(inst.src1, inst.src2); lhs = gpr.R(inst.src1); rhs = gpr.R(inst.src2); - FlushAll(); // For proper compare, we must sign extend so they both match or don't match. // But don't change pointers, in case one is SP (happens in LittleBigPlanet.) - if (XLEN >= 64 && gpr.IsMappedAsPointer(inst.src1)) { - ADDIW(SCRATCH1, lhs, 0); - lhs = SCRATCH1; - } else if (XLEN >= 64 && lhs != R_ZERO) { - ADDIW(lhs, lhs, 0); + if (gpr.IsMappedAsStaticPointer(inst.src1)) { + // We can't use SCRATCH1, which is destroyed by FlushAll... but cheat and use R_RA. + lhs = gpr.Normalize32(inst.src1, R_RA); + } else { + gpr.Normalize32(inst.src1); } - if (XLEN >= 64 && gpr.IsMappedAsPointer(inst.src2)) { - ADDIW(SCRATCH2, rhs, 0); - rhs = SCRATCH2; - } else if (XLEN >= 64 && rhs != R_ZERO) { - ADDIW(rhs, rhs, 0); + if (gpr.IsMappedAsStaticPointer(inst.src2)) { + rhs = gpr.Normalize32(inst.src2, SCRATCH2); + } else { + gpr.Normalize32(inst.src2); } + FlushAll(); + switch (inst.op) { case IROp::ExitToConstIfEq: fixup = BNE(lhs, rhs); @@ -117,16 +117,16 @@ void RiscVJit::CompIR_ExitIf(IRInst inst) { case IROp::ExitToConstIfLtZ: case IROp::ExitToConstIfLeZ: lhs = gpr.MapReg(inst.src1); - FlushAll(); // For proper compare, we must sign extend. - if (XLEN >= 64 && gpr.IsMappedAsPointer(inst.src1)) { - ADDIW(SCRATCH1, lhs, 0); - lhs = SCRATCH1; - } else if (XLEN >= 64 && lhs != R_ZERO) { - ADDIW(lhs, lhs, 0); + if (gpr.IsMappedAsPointer(inst.src1)) { + lhs = gpr.Normalize32(inst.src1, SCRATCH2); + } else { + gpr.Normalize32(inst.src1); } + FlushAll(); + switch (inst.op) { case IROp::ExitToConstIfGtZ: fixup = BGE(R_ZERO, lhs); diff --git a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp index 34cbd3f131..fd0ca6392d 100644 --- a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp +++ b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp @@ -82,9 +82,9 @@ void RiscVJit::CompIR_Load(IRInst inst) { } // If they're the same, MapReg may subtract MEMBASEREG, so just mark dirty. if (inst.dest == inst.src1) - gpr.MarkDirty(gpr.R(inst.dest)); + gpr.MarkDirty(gpr.R(inst.dest), true); else - gpr.MapReg(inst.dest, MAP_NOINIT); + gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); gpr.ReleaseSpillLock(inst.dest, inst.src1); s32 imm = AdjustForAddressOffset(&addrReg, inst.constant); @@ -109,7 +109,7 @@ void RiscVJit::CompIR_Load(IRInst inst) { break; case IROp::Load32: - LWU(gpr.R(inst.dest), addrReg, imm); + LW(gpr.R(inst.dest), addrReg, imm); break; default: diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index ff88d5c2c7..137faf025e 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -41,6 +41,7 @@ void RiscVRegCache::Start() { ar[i].isDirty = false; ar[i].pointerified = false; ar[i].tempLocked = false; + ar[i].normalized32 = false; } for (int i = 0; i < NUM_MIPSREG; i++) { mr[i].loc = MIPSLoc::MEM; @@ -62,6 +63,7 @@ void RiscVRegCache::Start() { // Treat R_ZERO a bit specially, but it's basically static alloc too. ar[R_ZERO].mipsReg = MIPS_REG_ZERO; + ar[R_ZERO].normalized32 = true; mr[MIPS_REG_ZERO].loc = MIPSLoc::RVREG_IMM; mr[MIPS_REG_ZERO].reg = R_ZERO; mr[MIPS_REG_ZERO].imm = 0; @@ -110,7 +112,7 @@ void RiscVRegCache::EmitLoadStaticRegisters() { const StaticAllocation *allocs = GetStaticAllocations(count); for (int i = 0; i < count; i++) { int offset = GetMipsRegOffset(allocs[i].mr); - emit_->LWU(allocs[i].ar, CTXREG, offset); + emit_->LW(allocs[i].ar, CTXREG, offset); if (allocs[i].pointerified && jo_->enablePointerify) { emit_->ADD(allocs[i].ar, allocs[i].ar, MEMBASEREG); } @@ -164,10 +166,28 @@ bool RiscVRegCache::IsMappedAsPointer(IRRegIndex mipsReg) { return false; } -void RiscVRegCache::MarkDirty(RiscVReg reg) { +bool RiscVRegCache::IsMappedAsStaticPointer(IRRegIndex reg) { + if (IsMappedAsPointer(reg)) { + return mr[reg].isStatic; + } + return false; +} + +bool RiscVRegCache::IsNormalized32(IRRegIndex mipsReg) { + _dbg_assert_(IsValidReg(mipsReg)); + if (XLEN == 32) + return true; + if (mr[mipsReg].loc == MIPSLoc::RVREG || mr[mipsReg].loc == MIPSLoc::RVREG_IMM) { + return ar[mr[mipsReg].reg].normalized32; + } + return false; +} + +void RiscVRegCache::MarkDirty(RiscVReg reg, bool andNormalized32) { // Can't mark X0 dirty. _dbg_assert_(reg > X0 && reg <= X31); ar[reg].isDirty = true; + ar[reg].normalized32 = andNormalized32; // If reg is written to, pointerification is lost. ar[reg].pointerified = false; if (ar[reg].mipsReg != IRREG_INVALID) { @@ -183,6 +203,7 @@ void RiscVRegCache::MarkDirty(RiscVReg reg) { void RiscVRegCache::MarkPtrDirty(RiscVReg reg) { // Can't mark X0 dirty. _dbg_assert_(reg > X0 && reg <= X31); + _dbg_assert_(!ar[reg].normalized32); ar[reg].isDirty = true; if (ar[reg].mipsReg != IRREG_INVALID) { _dbg_assert_(mr[ar[reg].mipsReg].loc == MIPSLoc::RVREG_AS_PTR); @@ -191,15 +212,67 @@ void RiscVRegCache::MarkPtrDirty(RiscVReg reg) { } } +RiscVGen::RiscVReg RiscVRegCache::Normalize32(IRRegIndex mipsReg, RiscVGen::RiscVReg destReg) { + _dbg_assert_(IsValidReg(mipsReg)); + _dbg_assert_(destReg == INVALID_REG || (destReg > X0 && destReg <= X31)); + + RiscVReg reg = mr[mipsReg].reg; + if (XLEN == 32) + return reg; + + switch (mr[mipsReg].loc) { + case MIPSLoc::IMM: + case MIPSLoc::MEM: + _assert_msg_(false, "Cannot normalize an imm or mem"); + return INVALID_REG; + + case MIPSLoc::RVREG: + case MIPSLoc::RVREG_IMM: + if (!ar[mr[mipsReg].reg].normalized32) { + if (destReg == INVALID_REG) { + emit_->ADDIW(mr[mipsReg].reg, mr[mipsReg].reg, 0); + ar[mr[mipsReg].reg].normalized32 = true; + ar[mr[mipsReg].reg].pointerified = false; + } else { + emit_->ADDIW(destReg, mr[mipsReg].reg, 0); + } + } else if (destReg != INVALID_REG) { + emit_->ADDIW(destReg, mr[mipsReg].reg, 0); + } + break; + + case MIPSLoc::RVREG_AS_PTR: + _dbg_assert_(ar[mr[mipsReg].reg].normalized32 == false); + if (destReg == INVALID_REG) { + // If we can pointerify, ADDIW will be enough. + if (!jo_->enablePointerify) + emit_->SUB(mr[mipsReg].reg, mr[mipsReg].reg, MEMBASEREG); + emit_->ADDIW(mr[mipsReg].reg, mr[mipsReg].reg, 0); + mr[mipsReg].loc = MIPSLoc::RVREG; + ar[mr[mipsReg].reg].normalized32 = true; + ar[mr[mipsReg].reg].pointerified = false; + } else if (!jo_->enablePointerify) { + emit_->SUB(destReg, mr[mipsReg].reg, MEMBASEREG); + emit_->ADDIW(destReg, destReg, 0); + } else { + emit_->ADDIW(destReg, mr[mipsReg].reg, 0); + } + break; + } + + return destReg == INVALID_REG ? reg : destReg; +} + void RiscVRegCache::SetRegImm(RiscVReg reg, u64 imm) { _dbg_assert_(reg != R_ZERO || imm == 0); _dbg_assert_(reg >= X0 && reg <= X31); // TODO: Could optimize this more for > 32 bit constants. emit_->LI(reg, imm); _dbg_assert_(!ar[reg].pointerified); + ar[reg].normalized32 = imm == (u64)(s64)(s32)imm; } -void RiscVRegCache::MapRegTo(RiscVReg reg, IRRegIndex mipsReg, int mapFlags) { +void RiscVRegCache::MapRegTo(RiscVReg reg, IRRegIndex mipsReg, MIPSMap mapFlags) { _dbg_assert_(reg > X0 && reg <= X31); _dbg_assert_(IsValidReg(mipsReg)); _dbg_assert_(!mr[mipsReg].isStatic); @@ -207,8 +280,8 @@ void RiscVRegCache::MapRegTo(RiscVReg reg, IRRegIndex mipsReg, int mapFlags) { ERROR_LOG(JIT, "Cannot MapRegTo static register %d", mipsReg); return; } - ar[reg].isDirty = (mapFlags & MAP_DIRTY) != 0; - if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { + ar[reg].isDirty = (mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY; + if ((mapFlags & MIPSMap::NOINIT) != MIPSMap::NOINIT) { if (mipsReg == MIPS_REG_ZERO) { // If we get a request to load the zero register, at least we won't spend // time on a memory access... @@ -217,11 +290,13 @@ void RiscVRegCache::MapRegTo(RiscVReg reg, IRRegIndex mipsReg, int mapFlags) { // This way, if we SetImm() it, we'll keep it. mr[mipsReg].loc = MIPSLoc::RVREG_IMM; mr[mipsReg].imm = 0; + ar[reg].normalized32 = true; } else { switch (mr[mipsReg].loc) { case MIPSLoc::MEM: - emit_->LWU(reg, CTXREG, GetMipsRegOffset(mipsReg)); + emit_->LW(reg, CTXREG, GetMipsRegOffset(mipsReg)); mr[mipsReg].loc = MIPSLoc::RVREG; + ar[reg].normalized32 = true; break; case MIPSLoc::IMM: SetRegImm(reg, mr[mipsReg].imm); @@ -230,7 +305,7 @@ void RiscVRegCache::MapRegTo(RiscVReg reg, IRRegIndex mipsReg, int mapFlags) { // If we are mapping dirty, it means we're gonna overwrite. // So the imm value is no longer valid. - if (mapFlags & MAP_DIRTY) + if ((mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY) mr[mipsReg].loc = MIPSLoc::RVREG; else mr[mipsReg].loc = MIPSLoc::RVREG_IMM; @@ -253,6 +328,8 @@ void RiscVRegCache::MapRegTo(RiscVReg reg, IRRegIndex mipsReg, int mapFlags) { } ar[reg].mipsReg = mipsReg; ar[reg].pointerified = false; + if (ar[reg].isDirty) + ar[reg].normalized32 = (mapFlags & MIPSMap::MARK_NORM32) == MIPSMap::MARK_NORM32; mr[mipsReg].reg = reg; } @@ -356,7 +433,7 @@ RiscVReg RiscVRegCache::GetAndLockTempR() { return reg; } -RiscVReg RiscVRegCache::MapReg(IRRegIndex mipsReg, int mapFlags) { +RiscVReg RiscVRegCache::MapReg(IRRegIndex mipsReg, MIPSMap mapFlags) { _dbg_assert_(IsValidReg(mipsReg)); // TODO: Optimization to force HI/LO to be combined? @@ -376,29 +453,37 @@ RiscVReg RiscVRegCache::MapReg(IRRegIndex mipsReg, int mapFlags) { if (mr[mipsReg].loc == MIPSLoc::IMM) { // Back into the register, with or without the imm value. // If noinit, the MAP_DIRTY check below will take care of the rest. - if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { + if ((mapFlags & MIPSMap::NOINIT) != MIPSMap::NOINIT) { + // This may set normalized32 to true. SetRegImm(riscvReg, mr[mipsReg].imm); mr[mipsReg].loc = MIPSLoc::RVREG_IMM; ar[riscvReg].pointerified = false; } + if ((mapFlags & MIPSMap::MARK_NORM32) == MIPSMap::MARK_NORM32) + ar[riscvReg].normalized32 = true; } else if (mr[mipsReg].loc == MIPSLoc::RVREG_AS_PTR) { // Was mapped as pointer, now we want it mapped as a value, presumably to // add or subtract stuff to it. - if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { + if ((mapFlags & MIPSMap::NOINIT) != MIPSMap::NOINIT) { #ifdef MASKED_PSP_MEMORY - _dbg_assert_(!ar[riscvReg].isDirty && (mapFlags & MAP_DIRTY) == 0); + _dbg_assert_(!ar[riscvReg].isDirty && (mapFlags & MIPSMap::DIRTY) != MIPSMap::DIRTY); #endif emit_->SUB(riscvReg, riscvReg, MEMBASEREG); + ar[riscvReg].normalized32 = false; } mr[mipsReg].loc = MIPSLoc::RVREG; + ar[riscvReg].normalized32 = false; } // Erasing the imm on dirty (necessary since otherwise we will still think it's ML_RVREG_IMM and return // true for IsImm and calculate crazily wrong things). /unknown - if (mapFlags & MAP_DIRTY) { + if ((mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY) { // As we are dirty, can't keep RVREG_IMM, we will quickly drift out of sync mr[mipsReg].loc = MIPSLoc::RVREG; ar[riscvReg].pointerified = false; ar[riscvReg].isDirty = true; + ar[riscvReg].normalized32 = (mapFlags & MIPSMap::MARK_NORM32) == MIPSMap::MARK_NORM32; + } else if ((mapFlags & MIPSMap::MARK_NORM32) == MIPSMap::MARK_NORM32) { + ar[riscvReg].normalized32 = true; } return mr[mipsReg].reg; } @@ -411,28 +496,33 @@ RiscVReg RiscVRegCache::MapReg(IRRegIndex mipsReg, int mapFlags) { if (ar[riscvReg].mipsReg != mipsReg) { ERROR_LOG_REPORT(JIT, "Register mapping out of sync! %i", mipsReg); } - if (mapFlags & MAP_DIRTY) { + if ((mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY) { // Mapping dirty means the old imm value is invalid. mr[mipsReg].loc = MIPSLoc::RVREG; ar[riscvReg].isDirty = true; // If reg is written to, pointerification is lost. ar[riscvReg].pointerified = false; + ar[riscvReg].normalized32 = (mapFlags & MIPSMap::MARK_NORM32) == MIPSMap::MARK_NORM32; + } else if ((mapFlags & MIPSMap::MARK_NORM32) == MIPSMap::MARK_NORM32) { + ar[riscvReg].normalized32 = true; } return mr[mipsReg].reg; } else if (mr[mipsReg].loc == MIPSLoc::RVREG_AS_PTR) { // Was mapped as pointer, now we want it mapped as a value, presumably to // add or subtract stuff to it. - if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { + if ((mapFlags & MIPSMap::NOINIT) != MIPSMap::NOINIT) { #ifdef MASKED_PSP_MEMORY _dbg_assert_(!ar[riscvReg].isDirty && (mapFlags & MAP_DIRTY) == 0); #endif emit_->SUB(riscvReg, riscvReg, MEMBASEREG); } mr[mipsReg].loc = MIPSLoc::RVREG; - if (mapFlags & MAP_DIRTY) { + if ((mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY) { ar[riscvReg].isDirty = true; } + // Let's always set this false, the SUB won't normalize. + ar[riscvReg].normalized32 = false; return mr[mipsReg].reg; } @@ -473,6 +563,7 @@ RiscVReg RiscVRegCache::MapRegAsPointer(IRRegIndex reg) { AddMemBase(riscvReg); ar[riscvReg].pointerified = true; } + ar[riscvReg].normalized32 = false; } else { ERROR_LOG(JIT, "MapRegAsPointer : MapReg failed to allocate a register?"); } @@ -498,6 +589,7 @@ void RiscVRegCache::AddMemBase(RiscVGen::RiscVReg reg) { emit_->ADD(reg, reg, MEMBASEREG); } #endif + ar[reg].normalized32 = false; } void RiscVRegCache::MapIn(IRRegIndex rs) { @@ -511,39 +603,43 @@ void RiscVRegCache::MapInIn(IRRegIndex rd, IRRegIndex rs) { ReleaseSpillLock(rd, rs); } -void RiscVRegCache::MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad) { +void RiscVRegCache::MapDirtyIn(IRRegIndex rd, IRRegIndex rs, MapType type) { SpillLock(rd, rs); - bool load = !avoidLoad || rd == rs; - MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT); + bool load = type == MapType::ALWAYS_LOAD || rd == rs; + MIPSMap norm32 = type == MapType::AVOID_LOAD_MARK_NORM32 ? MIPSMap::MARK_NORM32 : MIPSMap::INIT; + MapReg(rd, (load ? MIPSMap::DIRTY : MIPSMap::NOINIT) | norm32); MapReg(rs); ReleaseSpillLock(rd, rs); } -void RiscVRegCache::MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad) { +void RiscVRegCache::MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, MapType type) { SpillLock(rd, rs, rt); - bool load = !avoidLoad || (rd == rs || rd == rt); - MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT); + bool load = type == MapType::ALWAYS_LOAD || (rd == rs || rd == rt); + MIPSMap norm32 = type == MapType::AVOID_LOAD_MARK_NORM32 ? MIPSMap::MARK_NORM32 : MIPSMap::INIT; + MapReg(rd, (load ? MIPSMap::DIRTY : MIPSMap::NOINIT) | norm32); MapReg(rt); MapReg(rs); ReleaseSpillLock(rd, rs, rt); } -void RiscVRegCache::MapDirtyDirtyIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, bool avoidLoad) { +void RiscVRegCache::MapDirtyDirtyIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, MapType type) { SpillLock(rd1, rd2, rs); - bool load1 = !avoidLoad || rd1 == rs; - bool load2 = !avoidLoad || rd2 == rs; - MapReg(rd1, load1 ? MAP_DIRTY : MAP_NOINIT); - MapReg(rd2, load2 ? MAP_DIRTY : MAP_NOINIT); + bool load1 = type == MapType::ALWAYS_LOAD || rd1 == rs; + bool load2 = type == MapType::ALWAYS_LOAD || rd2 == rs; + MIPSMap norm32 = type == MapType::AVOID_LOAD_MARK_NORM32 ? MIPSMap::MARK_NORM32 : MIPSMap::INIT; + MapReg(rd1, (load1 ? MIPSMap::DIRTY : MIPSMap::NOINIT) | norm32); + MapReg(rd2, (load2 ? MIPSMap::DIRTY : MIPSMap::NOINIT) | norm32); MapReg(rs); ReleaseSpillLock(rd1, rd2, rs); } -void RiscVRegCache::MapDirtyDirtyInIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, IRRegIndex rt, bool avoidLoad) { +void RiscVRegCache::MapDirtyDirtyInIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, IRRegIndex rt, MapType type) { SpillLock(rd1, rd2, rs, rt); - bool load1 = !avoidLoad || (rd1 == rs || rd1 == rt); - bool load2 = !avoidLoad || (rd2 == rs || rd2 == rt); - MapReg(rd1, load1 ? MAP_DIRTY : MAP_NOINIT); - MapReg(rd2, load2 ? MAP_DIRTY : MAP_NOINIT); + bool load1 = type == MapType::ALWAYS_LOAD || (rd1 == rs || rd1 == rt); + bool load2 = type == MapType::ALWAYS_LOAD || (rd2 == rs || rd2 == rt); + MIPSMap norm32 = type == MapType::AVOID_LOAD_MARK_NORM32 ? MIPSMap::MARK_NORM32 : MIPSMap::INIT; + MapReg(rd1, (load1 ? MIPSMap::DIRTY : MIPSMap::NOINIT) | norm32); + MapReg(rd2, (load2 ? MIPSMap::DIRTY : MIPSMap::NOINIT) | norm32); MapReg(rt); MapReg(rs); ReleaseSpillLock(rd1, rd2, rs, rt); @@ -576,10 +672,11 @@ void RiscVRegCache::FlushRiscVReg(RiscVReg r) { if (mreg.loc == MIPSLoc::RVREG_AS_PTR) { // Unpointerify, in case dirty. #ifdef MASKED_PSP_MEMORY - _dbg_assert_(!ar[r].isDirty && (mapFlags & MAP_DIRTY) == 0); + _dbg_assert_(!ar[r].isDirty); #endif emit_->SUB(r, r, MEMBASEREG); mreg.loc = MIPSLoc::RVREG; + ar[r].normalized32 = false; } RiscVReg storeReg = RiscVRegForFlush(ar[r].mipsReg); if (storeReg != INVALID_REG) @@ -610,6 +707,7 @@ void RiscVRegCache::DiscardR(IRRegIndex mipsReg) { mr[mipsReg].loc = MIPSLoc::RVREG; ar[riscvReg].pointerified = false; ar[riscvReg].isDirty = false; + ar[riscvReg].normalized32 = false; } return; } @@ -620,6 +718,7 @@ void RiscVRegCache::DiscardR(IRRegIndex mipsReg) { ar[riscvReg].mipsReg = IRREG_INVALID; ar[riscvReg].pointerified = false; ar[riscvReg].isDirty = false; + ar[riscvReg].normalized32 = false; mr[mipsReg].reg = INVALID_REG; mr[mipsReg].loc = MIPSLoc::MEM; mr[mipsReg].imm = -1; @@ -833,6 +932,7 @@ void RiscVRegCache::SetImm(IRRegIndex r, u64 immVal) { mr[r].loc = MIPSLoc::IMM; mr[r].imm = immVal; ar[mr[r].reg].pointerified = false; + ar[mr[r].reg].normalized32 = false; // We do not change reg to INVALID_REG for obvious reasons.. } else { // Zap existing value if cached in a reg @@ -840,6 +940,7 @@ void RiscVRegCache::SetImm(IRRegIndex r, u64 immVal) { ar[mr[r].reg].mipsReg = IRREG_INVALID; ar[mr[r].reg].isDirty = false; ar[mr[r].reg].pointerified = false; + ar[mr[r].reg].normalized32 = false; } mr[r].loc = MIPSLoc::IMM; mr[r].imm = immVal; diff --git a/Core/MIPS/RiscV/RiscVRegCache.h b/Core/MIPS/RiscV/RiscVRegCache.h index 96a8272aef..3984748318 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.h +++ b/Core/MIPS/RiscV/RiscVRegCache.h @@ -47,9 +47,23 @@ enum class MIPSLoc { }; // Initing is the default so the flag is reversed. -enum { - MAP_DIRTY = 1, - MAP_NOINIT = 2 | MAP_DIRTY, +enum class MIPSMap { + INIT = 0, + DIRTY = 1, + NOINIT = 2 | DIRTY, + MARK_NORM32 = 4, +}; +static inline MIPSMap operator |(const MIPSMap &lhs, const MIPSMap &rhs) { + return MIPSMap((int)lhs | (int)rhs); +} +static inline MIPSMap operator &(const MIPSMap &lhs, const MIPSMap &rhs) { + return MIPSMap((int)lhs & (int)rhs); +} + +enum class MapType { + AVOID_LOAD, + AVOID_LOAD_MARK_NORM32, + ALWAYS_LOAD, }; } // namespace RiscVJitConstants @@ -69,8 +83,9 @@ constexpr IRRegIndex IRREG_INVALID = -1; struct RegStatusRiscV { IRRegIndex mipsReg; // if -1, no mipsreg attached. bool isDirty; // Should the register be written back? - bool pointerified; // Has added the memory base into the top part of the reg. Note - still usable as 32-bit reg (in some cases.) + bool pointerified; // Has added the memory base into the top part of the reg. Note - still usable as 32-bit reg (in only some cases.) bool tempLocked; // Reserved for a temp register. + bool normalized32; // 32 bits sign extended to XLEN. RISC-V can't always address just the low 32-bits, so this matters. }; struct RegStatusMIPS { @@ -110,21 +125,25 @@ public: RiscVGen::RiscVReg TryMapTempImm(IRRegIndex); // Returns an ARM register containing the requested MIPS register. - RiscVGen::RiscVReg MapReg(IRRegIndex reg, int mapFlags = 0); + RiscVGen::RiscVReg MapReg(IRRegIndex reg, RiscVJitConstants::MIPSMap mapFlags = RiscVJitConstants::MIPSMap::INIT); RiscVGen::RiscVReg MapRegAsPointer(IRRegIndex reg); bool IsMapped(IRRegIndex reg); bool IsMappedAsPointer(IRRegIndex reg); + bool IsMappedAsStaticPointer(IRRegIndex reg); bool IsInRAM(IRRegIndex reg); + bool IsNormalized32(IRRegIndex reg); - void MarkDirty(RiscVGen::RiscVReg reg); + void MarkDirty(RiscVGen::RiscVReg reg, bool andNormalized32 = false); void MarkPtrDirty(RiscVGen::RiscVReg reg); + // Copies to another reg if specified, otherwise same reg. + RiscVGen::RiscVReg Normalize32(IRRegIndex reg, RiscVGen::RiscVReg destReg = RiscVGen::INVALID_REG); void MapIn(IRRegIndex rs); void MapInIn(IRRegIndex rd, IRRegIndex rs); - void MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad = true); - void MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad = true); - void MapDirtyDirtyIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, bool avoidLoad = true); - void MapDirtyDirtyInIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, IRRegIndex rt, bool avoidLoad = true); + void MapDirtyIn(IRRegIndex rd, IRRegIndex rs, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD); + void MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD); + void MapDirtyDirtyIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD); + void MapDirtyDirtyInIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, IRRegIndex rt, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD); void FlushRiscVReg(RiscVGen::RiscVReg r); void FlushBeforeCall(); void FlushAll(); @@ -148,7 +167,7 @@ private: }; const StaticAllocation *GetStaticAllocations(int &count); const RiscVGen::RiscVReg *GetMIPSAllocationOrder(int &count); - void MapRegTo(RiscVGen::RiscVReg reg, IRRegIndex mipsReg, int mapFlags); + void MapRegTo(RiscVGen::RiscVReg reg, IRRegIndex mipsReg, RiscVJitConstants::MIPSMap mapFlags); RiscVGen::RiscVReg AllocateReg(); RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered); RiscVGen::RiscVReg RiscVRegForFlush(IRRegIndex r); From 5ed2f0d5599f458a2f161a212005cf9791d20034 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 22 Jul 2023 19:08:28 -0700 Subject: [PATCH 081/167] riscv: Implement logic ops. --- Core/MIPS/RiscV/RiscVAsm.cpp | 3 ++ Core/MIPS/RiscV/RiscVCompALU.cpp | 61 ++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVAsm.cpp b/Core/MIPS/RiscV/RiscVAsm.cpp index afdbeb75db..8232b0a286 100644 --- a/Core/MIPS/RiscV/RiscVAsm.cpp +++ b/Core/MIPS/RiscV/RiscVAsm.cpp @@ -206,7 +206,10 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { ADD(SCRATCH1, JITBASEREG, SCRATCH1); // TODO: Consider replacing the block nums after all, just trying to use IR block cache. LD(SCRATCH1, SCRATCH1, 0); + // TODO: This is ugly, but fixes a crash in LBP. May be a copied emuhack? + FixupBranch needCompile = BEQ(SCRATCH1, R_ZERO); JR(SCRATCH1); + SetJumpTarget(needCompile); SetJumpTarget(skipJump); // No block found, let's jit. We don't need to save static regs, they're all callee saved. diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index b9eb8c2df0..1f1ba8b295 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -74,14 +74,14 @@ void RiscVJit::CompIR_Arith(IRInst inst) { } } else { gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); - LI(SCRATCH1, (s32)inst.constant, SCRATCH2); + LI(SCRATCH1, (int32_t)inst.constant); ADDW(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1); } break; case IROp::SubConst: gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); - LI(SCRATCH1, (s32)inst.constant, SCRATCH2); + LI(SCRATCH1, (int32_t)inst.constant); SUBW(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1); break; @@ -101,13 +101,68 @@ void RiscVJit::CompIR_Logic(IRInst inst) { switch (inst.op) { case IROp::And: + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + AND(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + break; + case IROp::Or: + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + OR(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + // If both were normalized before, the result is normalized. + if (gpr.IsNormalized32(inst.src1) && gpr.IsNormalized32(inst.src2)) + gpr.MarkDirty(gpr.R(inst.dest), true); + break; + case IROp::Xor: + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + XOR(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + break; + case IROp::AndConst: + if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) { + gpr.MapDirtyIn(inst.dest, inst.src1); + ANDI(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant); + } else { + gpr.MapDirtyIn(inst.dest, inst.src1); + LI(SCRATCH1, (int32_t)inst.constant); + AND(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1); + } + // If the sign bits aren't cleared, and it was normalized before - it still is. + if ((inst.constant & 0x80000000) != 0 && gpr.IsNormalized32(inst.src1)) + gpr.MarkDirty(gpr.R(inst.dest), true); + // Otherwise, if we cleared the sign bits, it's naturally normalized. + else if ((inst.constant & 0x80000000) == 0) + gpr.MarkDirty(gpr.R(inst.dest), true); + break; + case IROp::OrConst: + if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) { + gpr.MapDirtyIn(inst.dest, inst.src1); + ORI(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant); + } else { + gpr.MapDirtyIn(inst.dest, inst.src1); + LI(SCRATCH1, (int32_t)inst.constant); + OR(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1); + } + // Since our constant is normalized, oring its bits in won't hurt normalization. + if (gpr.IsNormalized32(inst.src1)) + gpr.MarkDirty(gpr.R(inst.dest), true); + break; + case IROp::XorConst: + if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) { + gpr.MapDirtyIn(inst.dest, inst.src1); + XORI(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant); + } else { + gpr.MapDirtyIn(inst.dest, inst.src1); + LI(SCRATCH1, (int32_t)inst.constant); + XOR(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1); + } + break; + case IROp::Not: - CompIR_Generic(inst); + gpr.MapDirtyIn(inst.dest, inst.src1); + NOT(gpr.R(inst.dest), gpr.R(inst.src1)); break; default: From 2c7da94bd1a4c85f38a0812b9cbfedda4fe186d7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 22 Jul 2023 22:08:28 -0700 Subject: [PATCH 082/167] riscv: Implement shifts and compares. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 222 ++++++++++++++++++++++++++++-- Core/MIPS/RiscV/RiscVRegCache.cpp | 2 + 2 files changed, 210 insertions(+), 14 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index 1f1ba8b295..dd65bcef49 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -101,21 +101,37 @@ void RiscVJit::CompIR_Logic(IRInst inst) { switch (inst.op) { case IROp::And: - gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); - AND(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + if (inst.src1 != inst.src2) { + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + AND(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + } else if (inst.src1 != inst.dest) { + gpr.MapDirtyIn(inst.dest, inst.src1); + MV(gpr.R(inst.dest), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1)); + } break; case IROp::Or: - gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); - OR(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); - // If both were normalized before, the result is normalized. - if (gpr.IsNormalized32(inst.src1) && gpr.IsNormalized32(inst.src2)) - gpr.MarkDirty(gpr.R(inst.dest), true); + if (inst.src1 != inst.src2) { + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + OR(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + // If both were normalized before, the result is normalized. + if (gpr.IsNormalized32(inst.src1) && gpr.IsNormalized32(inst.src2)) + gpr.MarkDirty(gpr.R(inst.dest), true); + } else if (inst.src1 != inst.dest) { + gpr.MapDirtyIn(inst.dest, inst.src1); + MV(gpr.R(inst.dest), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1)); + } break; case IROp::Xor: - gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); - XOR(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + if (inst.src1 == inst.src2) { + gpr.SetImm(inst.dest, 0); + } else { + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + XOR(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + } break; case IROp::AndConst: @@ -176,9 +192,11 @@ void RiscVJit::CompIR_Assign(IRInst inst) { switch (inst.op) { case IROp::Mov: - gpr.MapDirtyIn(inst.dest, inst.src1); - MV(gpr.R(inst.dest), gpr.R(inst.src1)); - gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1)); + if (inst.dest != inst.src1) { + gpr.MapDirtyIn(inst.dest, inst.src1); + MV(gpr.R(inst.dest), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1)); + } break; case IROp::Ext8to32: @@ -250,14 +268,91 @@ void RiscVJit::CompIR_Shift(IRInst inst) { switch (inst.op) { case IROp::Shl: + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32); + SLLW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + break; + case IROp::Shr: + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32); + SRLW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + break; + case IROp::Sar: + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32); + SRAW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + break; + case IROp::Ror: + if (cpu_info.RiscV_Zbb) { + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32); + RORW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + } else { + CompIR_Generic(inst); + } + break; + case IROp::ShlImm: + // Shouldn't happen, but let's be safe of any passes that modify the ops. + if (inst.src2 >= 32) { + gpr.SetImm(inst.dest, 0); + } else if (inst.src2 == 0) { + if (inst.dest != inst.src1) { + gpr.MapDirtyIn(inst.dest, inst.src1); + MV(gpr.R(inst.dest), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1)); + } + } else { + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); + SLLIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.src2); + } + break; + case IROp::ShrImm: + // Shouldn't happen, but let's be safe of any passes that modify the ops. + if (inst.src2 >= 32) { + gpr.SetImm(inst.dest, 0); + } else if (inst.src2 == 0) { + if (inst.dest != inst.src1) { + gpr.MapDirtyIn(inst.dest, inst.src1); + MV(gpr.R(inst.dest), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1)); + } + } else { + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); + SRLIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.src2); + } + break; + case IROp::SarImm: + // Shouldn't happen, but let's be safe of any passes that modify the ops. + if (inst.src2 >= 32) { + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); + SRAIW(gpr.R(inst.dest), gpr.R(inst.src1), 31); + } else if (inst.src2 == 0) { + if (inst.dest != inst.src1) { + gpr.MapDirtyIn(inst.dest, inst.src1); + MV(gpr.R(inst.dest), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1)); + } + } else { + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); + SRAIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.src2); + } + break; + case IROp::RorImm: - CompIR_Generic(inst); + if (inst.src2 == 0) { + if (inst.dest != inst.src1) { + gpr.MapDirtyIn(inst.dest, inst.src1); + MV(gpr.R(inst.dest), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1)); + } + } else if (cpu_info.RiscV_Zbb) { + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); + RORIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.src2 & 31); + } else { + CompIR_Generic(inst); + } break; default: @@ -269,12 +364,111 @@ void RiscVJit::CompIR_Shift(IRInst inst) { void RiscVJit::CompIR_Compare(IRInst inst) { CONDITIONAL_DISABLE; + RiscVReg lhs = INVALID_REG; + RiscVReg rhs = INVALID_REG; + FixupBranch fixup; switch (inst.op) { case IROp::Slt: + // Not using the NORM32 flag so we don't confuse ourselves on overlap. + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + lhs = gpr.R(inst.src1); + rhs = gpr.R(inst.src2); + + // For proper compare, we must sign extend so they both match or don't match. + // But don't change pointers, in case one is SP (happens in LittleBigPlanet.) + if (gpr.IsMappedAsStaticPointer(inst.src1) || inst.src1 == inst.dest) { + lhs = gpr.Normalize32(inst.src1, SCRATCH1); + } else { + gpr.Normalize32(inst.src1); + } + if (gpr.IsMappedAsStaticPointer(inst.src2) || inst.src2 == inst.dest) { + rhs = gpr.Normalize32(inst.src2, SCRATCH2); + } else { + gpr.Normalize32(inst.src2); + } + + LI(gpr.R(inst.dest), 0); + fixup = BGE(lhs, rhs); + LI(gpr.R(inst.dest), 1); + SetJumpTarget(fixup); + + gpr.MarkDirty(gpr.R(inst.dest), true); + break; + case IROp::SltConst: + // Not using the NORM32 flag so we don't confuse ourselves on overlap. + gpr.MapDirtyIn(inst.dest, inst.src1); + if (inst.constant == 0) { + // Basically, getting the sign bit. Let's shift instead. + SRLIW(gpr.R(inst.dest), gpr.R(inst.src1), 31); + } else { + lhs = gpr.R(inst.src1); + if (gpr.IsMappedAsStaticPointer(inst.src1) || inst.src1 == inst.dest) { + lhs = gpr.Normalize32(inst.src1, SCRATCH1); + } else { + gpr.Normalize32(inst.src1); + } + + LI(SCRATCH2, (int32_t)inst.constant); + + LI(gpr.R(inst.dest), 0); + fixup = BGE(lhs, SCRATCH2); + LI(gpr.R(inst.dest), 1); + SetJumpTarget(fixup); + } + gpr.MarkDirty(gpr.R(inst.dest), true); + break; + case IROp::SltU: + // Not using the NORM32 flag so we don't confuse ourselves on overlap. + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + lhs = gpr.R(inst.src1); + rhs = gpr.R(inst.src2); + + // It's still fine to sign extend, the biggest just get even bigger. + if (gpr.IsMappedAsStaticPointer(inst.src1) || inst.src1 == inst.dest) { + lhs = gpr.Normalize32(inst.src1, SCRATCH1); + } else { + gpr.Normalize32(inst.src1); + } + if (gpr.IsMappedAsStaticPointer(inst.src2) || inst.src2 == inst.dest) { + rhs = gpr.Normalize32(inst.src2, SCRATCH2); + } else { + gpr.Normalize32(inst.src2); + } + + LI(gpr.R(inst.dest), 0); + fixup = BGEU(lhs, rhs); + LI(gpr.R(inst.dest), 1); + SetJumpTarget(fixup); + + gpr.MarkDirty(gpr.R(inst.dest), true); + break; + case IROp::SltUConst: - CompIR_Generic(inst); + // Not using the NORM32 flag so we don't confuse ourselves on overlap. + gpr.MapDirtyIn(inst.dest, inst.src1); + if (inst.constant == 0) { + gpr.SetImm(inst.dest, 0); + } else { + lhs = gpr.R(inst.src1); + if (gpr.IsMappedAsStaticPointer(inst.src1) || inst.src1 == inst.dest) { + lhs = gpr.Normalize32(inst.src1, SCRATCH1); + } else { + gpr.Normalize32(inst.src1); + } + + // We sign extend because we're comparing against something normalized. + // It's also the most efficient to set. + LI(SCRATCH2, (int32_t)inst.constant); + + LI(gpr.R(inst.dest), 0); + fixup = BGEU(lhs, SCRATCH2); + LI(gpr.R(inst.dest), 1); + SetJumpTarget(fixup); + + gpr.MarkDirty(gpr.R(inst.dest), true); + } break; default: diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index 137faf025e..ec55c3777c 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -239,6 +239,7 @@ RiscVGen::RiscVReg RiscVRegCache::Normalize32(IRRegIndex mipsReg, RiscVGen::Risc } else if (destReg != INVALID_REG) { emit_->ADDIW(destReg, mr[mipsReg].reg, 0); } + ar[mr[mipsReg].reg].pointerified = false; break; case MIPSLoc::RVREG_AS_PTR: @@ -257,6 +258,7 @@ RiscVGen::RiscVReg RiscVRegCache::Normalize32(IRRegIndex mipsReg, RiscVGen::Risc } else { emit_->ADDIW(destReg, mr[mipsReg].reg, 0); } + ar[mr[mipsReg].reg].pointerified = false; break; } From 92694e765f763ae208001f606c1d049f059368d1 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 22 Jul 2023 22:34:48 -0700 Subject: [PATCH 083/167] riscv: Implement conditional moves. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 63 +++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index dd65bcef49..554115afcb 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -480,12 +480,73 @@ void RiscVJit::CompIR_Compare(IRInst inst) { void RiscVJit::CompIR_CondAssign(IRInst inst) { CONDITIONAL_DISABLE; + RiscVReg lhs = INVALID_REG; + RiscVReg rhs = INVALID_REG; + FixupBranch fixup; switch (inst.op) { case IROp::MovZ: case IROp::MovNZ: + if (inst.dest == inst.src2) + return; + + // We could have a "zero" that with wrong upper due to XOR, so we have to normalize. + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::ALWAYS_LOAD); + lhs = gpr.R(inst.src1); + + if (gpr.IsMappedAsStaticPointer(inst.src1) || inst.src1 == inst.dest) { + lhs = gpr.Normalize32(inst.src1, SCRATCH1); + } else { + gpr.Normalize32(inst.src1); + } + + switch (inst.op) { + case IROp::MovZ: + fixup = BNE(lhs, R_ZERO); + break; + case IROp::MovNZ: + fixup = BEQ(lhs, R_ZERO); + break; + default: + INVALIDOP; + break; + } + + MV(gpr.R(inst.dest), gpr.R(inst.src2)); + SetJumpTarget(fixup); + break; + case IROp::Max: + if (inst.src1 != inst.src2) { + if (cpu_info.RiscV_Zbb) { + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + MAX(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + if (gpr.IsNormalized32(inst.src1) && gpr.IsNormalized32(inst.src2)) + gpr.MarkDirty(gpr.R(inst.dest), true); + } else { + CompIR_Generic(inst); + } + } else if (inst.dest != inst.src1) { + gpr.MapDirtyIn(inst.dest, inst.src1); + MV(gpr.R(inst.dest), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1)); + } + break; + case IROp::Min: - CompIR_Generic(inst); + if (inst.src1 != inst.src2) { + if (cpu_info.RiscV_Zbb) { + gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + MIN(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); + if (gpr.IsNormalized32(inst.src1) && gpr.IsNormalized32(inst.src2)) + gpr.MarkDirty(gpr.R(inst.dest), true); + } else { + CompIR_Generic(inst); + } + } else if (inst.dest != inst.src1) { + gpr.MapDirtyIn(inst.dest, inst.src1); + MV(gpr.R(inst.dest), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1)); + } break; default: From 34bfe93ea575a32f9f0d2c9ac0ef2371f533532d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 08:15:15 -0700 Subject: [PATCH 084/167] riscv: Fix block lookup issues. --- Core/MIPS/RiscV/RiscVAsm.cpp | 47 +++++++++++++++++++++++------ Core/MIPS/RiscV/RiscVCompBranch.cpp | 2 +- Core/MIPS/RiscV/RiscVCompSystem.cpp | 2 +- Core/MIPS/RiscV/RiscVJit.cpp | 14 +++++++-- Core/MIPS/RiscV/RiscVJit.h | 2 ++ 5 files changed, 52 insertions(+), 15 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVAsm.cpp b/Core/MIPS/RiscV/RiscVAsm.cpp index 8232b0a286..7fe1ce8b53 100644 --- a/Core/MIPS/RiscV/RiscVAsm.cpp +++ b/Core/MIPS/RiscV/RiscVAsm.cpp @@ -35,7 +35,7 @@ static const bool enableDisasm = false; static void ShowPC(u32 downcount, void *membase, void *jitbase) { static int count = 0; if (currentMIPS) { - ERROR_LOG(JIT, "ShowPC : %08x Downcount : %08x %d %p %p", currentMIPS->pc, downcount, count, membase, jitbase); + ERROR_LOG(JIT, "[%08x] ShowPC Downcount : %08x %d %p %p", currentMIPS->pc, downcount, count, membase, jitbase); } else { ERROR_LOG(JIT, "Universe corrupt?"); } @@ -44,6 +44,16 @@ static void ShowPC(u32 downcount, void *membase, void *jitbase) { count++; } +static void ShowBlockError(int type) { + if (type == 1) { + ERROR_LOG(JIT, "[%08x] ShowBlockError: block num was out of range in emuhack", currentMIPS->pc); + } else if (type == 2) { + ERROR_LOG(JIT, "[%08x] ShowBlockError: block num pointed to null jitblock", currentMIPS->pc); + } else { + ERROR_LOG(JIT, "[%08x] ShowBlockError: invalid error type", currentMIPS->pc); + } +} + void RiscVJit::GenerateFixedCode(const JitOptions &jo) { BeginWrite(GetMemoryProtectPageSize()); const u8 *start = AlignCodePage(); @@ -197,20 +207,37 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { SRLI(SCRATCH2, SCRATCH1, 24); // We're in other words comparing to the top 8 bits of MIPS_EMUHACK_OPCODE by subtracting. ADDI(SCRATCH2, SCRATCH2, -(MIPS_EMUHACK_OPCODE >> 24)); - FixupBranch skipJump = BNE(SCRATCH2, R_ZERO); + FixupBranch needsCompile = BNE(SCRATCH2, R_ZERO); // Use a wall to mask by 0x00FFFFFF and extract the block number. SLLI(SCRATCH1, SCRATCH1, XLEN - 24); // But actually, we want * 8, so skip shifting back just a bit. _assert_msg_(sizeof(blockStartAddrs_[0]) == 8, "RiscVAsm currently assumes pointers are 64-bit"); SRLI(SCRATCH1, SCRATCH1, XLEN - 24 - 3); - ADD(SCRATCH1, JITBASEREG, SCRATCH1); - // TODO: Consider replacing the block nums after all, just trying to use IR block cache. - LD(SCRATCH1, SCRATCH1, 0); - // TODO: This is ugly, but fixes a crash in LBP. May be a copied emuhack? - FixupBranch needCompile = BEQ(SCRATCH1, R_ZERO); - JR(SCRATCH1); - SetJumpTarget(needCompile); - SetJumpTarget(skipJump); + if (enableDebug) { + // Let's do some extra validation of the block number in debug mode for testing. + + LI(SCRATCH2, MAX_ALLOWED_JIT_BLOCKS * 8); + FixupBranch highBlockNum = BGEU(SCRATCH1, SCRATCH2); + ADD(SCRATCH1, JITBASEREG, SCRATCH1); + // TODO: Consider replacing the block nums after all, just trying to use IR block cache. + LD(SCRATCH1, SCRATCH1, 0); + LI(SCRATCH2, 2); + FixupBranch invalidBlockNum = BEQ(SCRATCH1, R_ZERO); + JR(SCRATCH1); + + SetJumpTarget(highBlockNum); + LI(SCRATCH2, 1); + SetJumpTarget(invalidBlockNum); + + MV(X10, SCRATCH2); + QuickCallFunction(&ShowBlockError); + } else { + ADD(SCRATCH1, JITBASEREG, SCRATCH1); + // TODO: Consider replacing the block nums after all, just trying to use IR block cache. + LD(SCRATCH1, SCRATCH1, 0); + JR(SCRATCH1); + } + SetJumpTarget(needsCompile); // No block found, let's jit. We don't need to save static regs, they're all callee saved. RestoreRoundingMode(true); diff --git a/Core/MIPS/RiscV/RiscVCompBranch.cpp b/Core/MIPS/RiscV/RiscVCompBranch.cpp index b3e4247df5..d26a52bdd2 100644 --- a/Core/MIPS/RiscV/RiscVCompBranch.cpp +++ b/Core/MIPS/RiscV/RiscVCompBranch.cpp @@ -19,7 +19,7 @@ #include "Core/MIPS/RiscV/RiscVJit.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" -// This file contains compilation for exits, syscalls, and related. +// This file contains compilation for exits. // // All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. // Currently known non working ones should have DISABLE. No flags because that's in IR already. diff --git a/Core/MIPS/RiscV/RiscVCompSystem.cpp b/Core/MIPS/RiscV/RiscVCompSystem.cpp index d097915d0c..6c3bd07f78 100644 --- a/Core/MIPS/RiscV/RiscVCompSystem.cpp +++ b/Core/MIPS/RiscV/RiscVCompSystem.cpp @@ -19,7 +19,7 @@ #include "Core/MIPS/RiscV/RiscVJit.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" -// This file contains compilation for basic PC/downcount accounting, debug funcs, etc. +// This file contains compilation for basic PC/downcount accounting, syscalls, debug funcs, etc. // // All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. // Currently known non working ones should have DISABLE. No flags because that's in IR already. diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 054b80808e..64b24f0ee5 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -26,8 +26,6 @@ namespace MIPSComp { using namespace RiscVGen; using namespace RiscVJitConstants; -static constexpr int MAX_ALLOWED_JIT_BLOCKS = 262144; - RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo) { // Automatically disable incompatible options. if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) { @@ -39,6 +37,7 @@ RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo) // TODO: Consider replacing block num method form IRJit - this is 2MB. blockStartAddrs_ = new const u8 *[MAX_ALLOWED_JIT_BLOCKS]; + memset(blockStartAddrs_, 0, sizeof(blockStartAddrs_[0]) * MAX_ALLOWED_JIT_BLOCKS); gpr.Init(this); // TODO: fpr @@ -65,7 +64,16 @@ bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u // TODO: Block linking, checked entries and such. - int block_num = blocks_.GetBlockNumberFromStartAddress(em_address); + int block_num; + if (preload) { + block_num = blocks_.GetBlockNumberFromStartAddress(em_address); + } else { + u32 first_inst = Memory::ReadUnchecked_U32(em_address); + _assert_msg_(MIPS_IS_RUNBLOCK(first_inst), "Should've written an emuhack"); + + block_num = first_inst & MIPS_EMUHACK_VALUE_MASK; + } + _assert_msg_(block_num >= 0 && block_num < MAX_ALLOWED_JIT_BLOCKS, "Bad block num"); _assert_msg_(blockStartAddrs_[block_num] == nullptr, "Block %d reused before clear", block_num); blockStartAddrs_[block_num] = GetCodePointer(); diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index f50d1b6782..97d1eaf603 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -111,6 +111,8 @@ private: RiscVRegCache gpr; + static constexpr int MAX_ALLOWED_JIT_BLOCKS = 262144; + const u8 *enterDispatcher_ = nullptr; const u8 *outerLoop_ = nullptr; From f7f7531500c671722c0999acbe8f53134a2ecb25 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 09:48:44 -0700 Subject: [PATCH 085/167] riscv: Fix min/max normalization. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 67 ++++++----------------------- Core/MIPS/RiscV/RiscVCompBranch.cpp | 30 ++----------- Core/MIPS/RiscV/RiscVJit.cpp | 25 ++++++++++- Core/MIPS/RiscV/RiscVJit.h | 3 ++ 4 files changed, 43 insertions(+), 82 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index 554115afcb..6f2d455f9f 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -371,21 +371,7 @@ void RiscVJit::CompIR_Compare(IRInst inst) { case IROp::Slt: // Not using the NORM32 flag so we don't confuse ourselves on overlap. gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); - lhs = gpr.R(inst.src1); - rhs = gpr.R(inst.src2); - - // For proper compare, we must sign extend so they both match or don't match. - // But don't change pointers, in case one is SP (happens in LittleBigPlanet.) - if (gpr.IsMappedAsStaticPointer(inst.src1) || inst.src1 == inst.dest) { - lhs = gpr.Normalize32(inst.src1, SCRATCH1); - } else { - gpr.Normalize32(inst.src1); - } - if (gpr.IsMappedAsStaticPointer(inst.src2) || inst.src2 == inst.dest) { - rhs = gpr.Normalize32(inst.src2, SCRATCH2); - } else { - gpr.Normalize32(inst.src2); - } + NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, false); LI(gpr.R(inst.dest), 0); fixup = BGE(lhs, rhs); @@ -402,13 +388,7 @@ void RiscVJit::CompIR_Compare(IRInst inst) { // Basically, getting the sign bit. Let's shift instead. SRLIW(gpr.R(inst.dest), gpr.R(inst.src1), 31); } else { - lhs = gpr.R(inst.src1); - if (gpr.IsMappedAsStaticPointer(inst.src1) || inst.src1 == inst.dest) { - lhs = gpr.Normalize32(inst.src1, SCRATCH1); - } else { - gpr.Normalize32(inst.src1); - } - + NormalizeSrc1(inst, &lhs, SCRATCH1, false); LI(SCRATCH2, (int32_t)inst.constant); LI(gpr.R(inst.dest), 0); @@ -422,20 +402,8 @@ void RiscVJit::CompIR_Compare(IRInst inst) { case IROp::SltU: // Not using the NORM32 flag so we don't confuse ourselves on overlap. gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); - lhs = gpr.R(inst.src1); - rhs = gpr.R(inst.src2); - // It's still fine to sign extend, the biggest just get even bigger. - if (gpr.IsMappedAsStaticPointer(inst.src1) || inst.src1 == inst.dest) { - lhs = gpr.Normalize32(inst.src1, SCRATCH1); - } else { - gpr.Normalize32(inst.src1); - } - if (gpr.IsMappedAsStaticPointer(inst.src2) || inst.src2 == inst.dest) { - rhs = gpr.Normalize32(inst.src2, SCRATCH2); - } else { - gpr.Normalize32(inst.src2); - } + NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, false); LI(gpr.R(inst.dest), 0); fixup = BGEU(lhs, rhs); @@ -451,12 +419,7 @@ void RiscVJit::CompIR_Compare(IRInst inst) { if (inst.constant == 0) { gpr.SetImm(inst.dest, 0); } else { - lhs = gpr.R(inst.src1); - if (gpr.IsMappedAsStaticPointer(inst.src1) || inst.src1 == inst.dest) { - lhs = gpr.Normalize32(inst.src1, SCRATCH1); - } else { - gpr.Normalize32(inst.src1); - } + NormalizeSrc1(inst, &lhs, SCRATCH1, false); // We sign extend because we're comparing against something normalized. // It's also the most efficient to set. @@ -491,13 +454,7 @@ void RiscVJit::CompIR_CondAssign(IRInst inst) { // We could have a "zero" that with wrong upper due to XOR, so we have to normalize. gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::ALWAYS_LOAD); - lhs = gpr.R(inst.src1); - - if (gpr.IsMappedAsStaticPointer(inst.src1) || inst.src1 == inst.dest) { - lhs = gpr.Normalize32(inst.src1, SCRATCH1); - } else { - gpr.Normalize32(inst.src1); - } + NormalizeSrc1(inst, &lhs, SCRATCH1, true); switch (inst.op) { case IROp::MovZ: @@ -519,9 +476,10 @@ void RiscVJit::CompIR_CondAssign(IRInst inst) { if (inst.src1 != inst.src2) { if (cpu_info.RiscV_Zbb) { gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); - MAX(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); - if (gpr.IsNormalized32(inst.src1) && gpr.IsNormalized32(inst.src2)) - gpr.MarkDirty(gpr.R(inst.dest), true); + NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true); + MAX(gpr.R(inst.dest), lhs, rhs); + // Because we had to normalize the inputs, the output is normalized. + gpr.MarkDirty(gpr.R(inst.dest), true); } else { CompIR_Generic(inst); } @@ -536,9 +494,10 @@ void RiscVJit::CompIR_CondAssign(IRInst inst) { if (inst.src1 != inst.src2) { if (cpu_info.RiscV_Zbb) { gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); - MIN(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2)); - if (gpr.IsNormalized32(inst.src1) && gpr.IsNormalized32(inst.src2)) - gpr.MarkDirty(gpr.R(inst.dest), true); + NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true); + MIN(gpr.R(inst.dest), lhs, rhs); + // Because we had to normalize the inputs, the output is normalized. + gpr.MarkDirty(gpr.R(inst.dest), true); } else { CompIR_Generic(inst); } diff --git a/Core/MIPS/RiscV/RiscVCompBranch.cpp b/Core/MIPS/RiscV/RiscVCompBranch.cpp index d26a52bdd2..c477a5fcae 100644 --- a/Core/MIPS/RiscV/RiscVCompBranch.cpp +++ b/Core/MIPS/RiscV/RiscVCompBranch.cpp @@ -74,23 +74,8 @@ void RiscVJit::CompIR_ExitIf(IRInst inst) { case IROp::ExitToConstIfEq: case IROp::ExitToConstIfNeq: gpr.MapInIn(inst.src1, inst.src2); - lhs = gpr.R(inst.src1); - rhs = gpr.R(inst.src2); - - // For proper compare, we must sign extend so they both match or don't match. - // But don't change pointers, in case one is SP (happens in LittleBigPlanet.) - if (gpr.IsMappedAsStaticPointer(inst.src1)) { - // We can't use SCRATCH1, which is destroyed by FlushAll... but cheat and use R_RA. - lhs = gpr.Normalize32(inst.src1, R_RA); - } else { - gpr.Normalize32(inst.src1); - } - if (gpr.IsMappedAsStaticPointer(inst.src2)) { - rhs = gpr.Normalize32(inst.src2, SCRATCH2); - } else { - gpr.Normalize32(inst.src2); - } - + // We can't use SCRATCH1, which is destroyed by FlushAll()... but cheat and use R_RA. + NormalizeSrc12(inst, &lhs, &rhs, R_RA, SCRATCH2, true); FlushAll(); switch (inst.op) { @@ -116,15 +101,8 @@ void RiscVJit::CompIR_ExitIf(IRInst inst) { case IROp::ExitToConstIfGeZ: case IROp::ExitToConstIfLtZ: case IROp::ExitToConstIfLeZ: - lhs = gpr.MapReg(inst.src1); - - // For proper compare, we must sign extend. - if (gpr.IsMappedAsPointer(inst.src1)) { - lhs = gpr.Normalize32(inst.src1, SCRATCH2); - } else { - gpr.Normalize32(inst.src1); - } - + gpr.MapReg(inst.src1); + NormalizeSrc1(inst, &lhs, SCRATCH2, true); FlushAll(); switch (inst.op) { diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 64b24f0ee5..980e5b6347 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -508,11 +508,11 @@ void RiscVJit::ApplyRoundingMode(bool force) { //QuickCallFunction(applyRoundingMode_); } -void RiscVJit::MovFromPC(RiscVGen::RiscVReg r) { +void RiscVJit::MovFromPC(RiscVReg r) { LWU(r, CTXREG, offsetof(MIPSState, pc)); } -void RiscVJit::MovToPC(RiscVGen::RiscVReg r) { +void RiscVJit::MovToPC(RiscVReg r) { SW(r, CTXREG, offsetof(MIPSState, pc)); } @@ -533,4 +533,25 @@ void RiscVJit::LoadStaticRegisters() { } } +void RiscVJit::NormalizeSrc1(IRInst inst, RiscVReg *reg, RiscVReg tempReg, bool allowOverlap) { + *reg = NormalizeR(inst.src1, allowOverlap ? 0 : inst.dest, tempReg); +} + +void RiscVJit::NormalizeSrc12(IRInst inst, RiscVReg *lhs, RiscVReg *rhs, RiscVReg lhsTempReg, RiscVReg rhsTempReg, bool allowOverlap) { + *lhs = NormalizeR(inst.src1, allowOverlap ? 0 : inst.dest, lhsTempReg); + *rhs = NormalizeR(inst.src2, allowOverlap ? 0 : inst.dest, rhsTempReg); +} + +RiscVReg RiscVJit::NormalizeR(IRRegIndex rs, IRRegIndex rd, RiscVReg tempReg) { + // For proper compare, we must sign extend so they both match or don't match. + // But don't change pointers, in case one is SP (happens in LittleBigPlanet.) + if (gpr.IsImm(rs) && gpr.GetImm(rs) == 0) { + return R_ZERO; + } else if (gpr.IsMappedAsPointer(rs) || rs == rd) { + return gpr.Normalize32(rs, tempReg); + } else { + return gpr.Normalize32(rs); + } +} + } // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index 97d1eaf603..5f32aee999 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -108,6 +108,9 @@ private: void SetScratch1ToSrc1Address(IRReg src1); // Modifies SCRATCH regs. int32_t AdjustForAddressOffset(RiscVGen::RiscVReg *reg, int32_t constant); + void NormalizeSrc1(IRInst inst, RiscVGen::RiscVReg *reg, RiscVGen::RiscVReg tempReg, bool allowOverlap); + void NormalizeSrc12(IRInst inst, RiscVGen::RiscVReg *lhs, RiscVGen::RiscVReg *rhs, RiscVGen::RiscVReg lhsTempReg, RiscVGen::RiscVReg rhsTempReg, bool allowOverlap); + RiscVGen::RiscVReg NormalizeR(IRRegIndex rs, IRRegIndex rd, RiscVGen::RiscVReg tempReg); RiscVRegCache gpr; From 4100767b5e744d9043a94b6d7d239eccb990c780 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 09:52:05 -0700 Subject: [PATCH 086/167] riscv: Optimize SetConst a bit. --- Core/MIPS/RiscV/RiscVCompSystem.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/RiscV/RiscVCompSystem.cpp b/Core/MIPS/RiscV/RiscVCompSystem.cpp index 6c3bd07f78..63eb555d31 100644 --- a/Core/MIPS/RiscV/RiscVCompSystem.cpp +++ b/Core/MIPS/RiscV/RiscVCompSystem.cpp @@ -39,7 +39,9 @@ void RiscVJit::CompIR_Basic(IRInst inst) { switch (inst.op) { case IROp::SetConst: - gpr.SetImm(inst.dest, inst.constant); + // Sign extend all constants. We get 0xFFFFFFFF sometimes, and it's more work to truncate. + // The register only holds 32 bits in the end anyway. + gpr.SetImm(inst.dest, (int32_t)inst.constant); break; case IROp::SetConstF: From ca15fa706127a5772d09b9e9f24b4ed1a4ea442e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 14:20:57 -0700 Subject: [PATCH 087/167] riscv: Enable jit by default. --- Core/Config.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 81b41e2646..da1c279033 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -129,7 +129,7 @@ std::string CreateRandMAC() { } static int DefaultCpuCore() { -#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) +#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(RISCV64) if (System_GetPropertyBool(SYSPROP_CAN_JIT)) return (int)CPUCore::JIT; return (int)CPUCore::IR_JIT; @@ -139,7 +139,7 @@ static int DefaultCpuCore() { } static bool DefaultCodeGen() { -#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) +#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(RISCV64) return true; #else return false; From 7f4689e8fafaa62459ccc87e1413f9274ff9e04d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 16:24:49 -0700 Subject: [PATCH 088/167] riscv: Use direct SLI/SLIU instructions. Derp, I forgot these existed on RISC-V for a moment. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 42 ++++++++++++-------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index 6f2d455f9f..84f3dcfa67 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -366,18 +366,12 @@ void RiscVJit::CompIR_Compare(IRInst inst) { RiscVReg lhs = INVALID_REG; RiscVReg rhs = INVALID_REG; - FixupBranch fixup; switch (inst.op) { case IROp::Slt: // Not using the NORM32 flag so we don't confuse ourselves on overlap. gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); - NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, false); - - LI(gpr.R(inst.dest), 0); - fixup = BGE(lhs, rhs); - LI(gpr.R(inst.dest), 1); - SetJumpTarget(fixup); - + NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true); + SLT(gpr.R(inst.dest), lhs, rhs); gpr.MarkDirty(gpr.R(inst.dest), true); break; @@ -389,12 +383,13 @@ void RiscVJit::CompIR_Compare(IRInst inst) { SRLIW(gpr.R(inst.dest), gpr.R(inst.src1), 31); } else { NormalizeSrc1(inst, &lhs, SCRATCH1, false); - LI(SCRATCH2, (int32_t)inst.constant); - LI(gpr.R(inst.dest), 0); - fixup = BGE(lhs, SCRATCH2); - LI(gpr.R(inst.dest), 1); - SetJumpTarget(fixup); + if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) { + SLTI(gpr.R(inst.dest), lhs, (int32_t)inst.constant); + } else { + LI(SCRATCH2, (int32_t)inst.constant); + SLT(gpr.R(inst.dest), lhs, SCRATCH2); + } } gpr.MarkDirty(gpr.R(inst.dest), true); break; @@ -403,13 +398,8 @@ void RiscVJit::CompIR_Compare(IRInst inst) { // Not using the NORM32 flag so we don't confuse ourselves on overlap. gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); // It's still fine to sign extend, the biggest just get even bigger. - NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, false); - - LI(gpr.R(inst.dest), 0); - fixup = BGEU(lhs, rhs); - LI(gpr.R(inst.dest), 1); - SetJumpTarget(fixup); - + NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true); + SLTU(gpr.R(inst.dest), lhs, rhs); gpr.MarkDirty(gpr.R(inst.dest), true); break; @@ -423,12 +413,12 @@ void RiscVJit::CompIR_Compare(IRInst inst) { // We sign extend because we're comparing against something normalized. // It's also the most efficient to set. - LI(SCRATCH2, (int32_t)inst.constant); - - LI(gpr.R(inst.dest), 0); - fixup = BGEU(lhs, SCRATCH2); - LI(gpr.R(inst.dest), 1); - SetJumpTarget(fixup); + if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) { + SLTIU(gpr.R(inst.dest), lhs, (int32_t)inst.constant); + } else { + LI(SCRATCH2, (int32_t)inst.constant); + SLTU(gpr.R(inst.dest), lhs, SCRATCH2); + } gpr.MarkDirty(gpr.R(inst.dest), true); } From 18c48681a83846ea4214ff6ef3e1453a9428871a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 17:09:05 -0700 Subject: [PATCH 089/167] riscv: Implement multiply instructions. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 101 ++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index 84f3dcfa67..61c0da3a70 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -509,10 +509,27 @@ void RiscVJit::CompIR_HiLo(IRInst inst) { switch (inst.op) { case IROp::MtLo: + gpr.MapDirtyIn(IRREG_LO, inst.src1); + MV(gpr.R(IRREG_LO), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(IRREG_LO), gpr.IsNormalized32(inst.src1)); + break; + case IROp::MtHi: + gpr.MapDirtyIn(IRREG_HI, inst.src1); + MV(gpr.R(IRREG_HI), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(IRREG_HI), gpr.IsNormalized32(inst.src1)); + break; + case IROp::MfLo: + gpr.MapDirtyIn(inst.dest, IRREG_LO); + MV(gpr.R(inst.dest), gpr.R(IRREG_LO)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(IRREG_LO)); + break; + case IROp::MfHi: - CompIR_Generic(inst); + gpr.MapDirtyIn(inst.dest, IRREG_HI); + MV(gpr.R(inst.dest), gpr.R(IRREG_HI)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(IRREG_HI)); break; default: @@ -524,14 +541,94 @@ void RiscVJit::CompIR_HiLo(IRInst inst) { void RiscVJit::CompIR_Mult(IRInst inst) { CONDITIONAL_DISABLE; + auto makeArgsUnsigned = [&](RiscVReg *lhs, RiscVReg *rhs) { + if (cpu_info.RiscV_Zba) { + ZEXT_W(SCRATCH1, gpr.R(inst.src1)); + ZEXT_W(SCRATCH2, gpr.R(inst.src2)); + } else { + SLLI(SCRATCH1, gpr.R(inst.src1), XLEN - 32); + SRLI(SCRATCH1, SCRATCH1, XLEN - 32); + SLLI(SCRATCH2, gpr.R(inst.src2), XLEN - 32); + SRLI(SCRATCH2, SCRATCH2, XLEN - 32); + } + *lhs = SCRATCH1; + *rhs = SCRATCH2; + }; + auto combinePrevMulResult = [&] { + // TODO: Using a single reg for HI/LO would make this less ugly. + if (cpu_info.RiscV_Zba) { + ZEXT_W(gpr.R(IRREG_LO), gpr.R(IRREG_LO)); + } else { + SLLI(gpr.R(IRREG_LO), gpr.R(IRREG_LO), XLEN - 32); + SRLI(gpr.R(IRREG_LO), gpr.R(IRREG_LO), XLEN - 32); + } + SLLI(gpr.R(IRREG_HI), gpr.R(IRREG_HI), 32); + OR(gpr.R(IRREG_LO), gpr.R(IRREG_LO), gpr.R(IRREG_HI)); + }; + auto splitMulResult = [&] { + SRAI(gpr.R(IRREG_HI), gpr.R(IRREG_LO), 32); + gpr.MarkDirty(gpr.R(IRREG_HI), true); + }; + + RiscVReg lhs = INVALID_REG; + RiscVReg rhs = INVALID_REG; switch (inst.op) { case IROp::Mult: + // TODO: Maybe IR could simplify when HI is not needed or clobbered? + // TODO: HI/LO merge optimization? Have to be careful of passes that split them... + gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2); + NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true); + MUL(gpr.R(IRREG_LO), lhs, rhs); + splitMulResult(); + break; + case IROp::MultU: + // This is an "anti-norm32" case. Let's just zero always. + // TODO: If we could know that LO was only needed, we could use MULW and be done. + gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2); + makeArgsUnsigned(&lhs, &rhs); + MUL(gpr.R(IRREG_LO), lhs, rhs); + splitMulResult(); + break; + case IROp::Madd: + gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::ALWAYS_LOAD); + NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true); + MUL(SCRATCH1, lhs, rhs); + + combinePrevMulResult(); + ADD(gpr.R(IRREG_LO), gpr.R(IRREG_LO), SCRATCH1); + splitMulResult(); + break; + case IROp::MaddU: + gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::ALWAYS_LOAD); + makeArgsUnsigned(&lhs, &rhs); + MUL(SCRATCH1, lhs, rhs); + + combinePrevMulResult(); + ADD(gpr.R(IRREG_LO), gpr.R(IRREG_LO), SCRATCH1); + splitMulResult(); + break; + case IROp::Msub: + gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::ALWAYS_LOAD); + NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true); + MUL(SCRATCH1, lhs, rhs); + + combinePrevMulResult(); + SUB(gpr.R(IRREG_LO), gpr.R(IRREG_LO), SCRATCH1); + splitMulResult(); + break; + case IROp::MsubU: - CompIR_Generic(inst); + gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::ALWAYS_LOAD); + makeArgsUnsigned(&lhs, &rhs); + MUL(SCRATCH1, lhs, rhs); + + combinePrevMulResult(); + SUB(gpr.R(IRREG_LO), gpr.R(IRREG_LO), SCRATCH1); + splitMulResult(); break; default: From b6f83ca969473a7947c90754ecec915cb5489eea Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 21:17:39 -0700 Subject: [PATCH 090/167] riscv: Cleanup some pointerification flags. --- Core/MIPS/RiscV/RiscVRegCache.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index ec55c3777c..6ddf909977 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -55,6 +55,7 @@ void RiscVRegCache::Start() { for (int i = 0; i < numStatics; i++) { ar[statics[i].ar].mipsReg = statics[i].mr; ar[statics[i].ar].pointerified = statics[i].pointerified && jo_->enablePointerify; + ar[statics[i].ar].normalized32 = false; mr[statics[i].mr].loc = MIPSLoc::RVREG; mr[statics[i].mr].reg = statics[i].ar; mr[statics[i].mr].isStatic = true; @@ -112,9 +113,11 @@ void RiscVRegCache::EmitLoadStaticRegisters() { const StaticAllocation *allocs = GetStaticAllocations(count); for (int i = 0; i < count; i++) { int offset = GetMipsRegOffset(allocs[i].mr); - emit_->LW(allocs[i].ar, CTXREG, offset); if (allocs[i].pointerified && jo_->enablePointerify) { + emit_->LWU(allocs[i].ar, CTXREG, offset); emit_->ADD(allocs[i].ar, allocs[i].ar, MEMBASEREG); + } else { + emit_->LW(allocs[i].ar, CTXREG, offset); } } } @@ -230,35 +233,33 @@ RiscVGen::RiscVReg RiscVRegCache::Normalize32(IRRegIndex mipsReg, RiscVGen::Risc case MIPSLoc::RVREG_IMM: if (!ar[mr[mipsReg].reg].normalized32) { if (destReg == INVALID_REG) { - emit_->ADDIW(mr[mipsReg].reg, mr[mipsReg].reg, 0); + emit_->SEXT_W(mr[mipsReg].reg, mr[mipsReg].reg); ar[mr[mipsReg].reg].normalized32 = true; ar[mr[mipsReg].reg].pointerified = false; } else { - emit_->ADDIW(destReg, mr[mipsReg].reg, 0); + emit_->SEXT_W(destReg, mr[mipsReg].reg); } } else if (destReg != INVALID_REG) { - emit_->ADDIW(destReg, mr[mipsReg].reg, 0); + emit_->SEXT_W(destReg, mr[mipsReg].reg); } - ar[mr[mipsReg].reg].pointerified = false; break; case MIPSLoc::RVREG_AS_PTR: _dbg_assert_(ar[mr[mipsReg].reg].normalized32 == false); if (destReg == INVALID_REG) { - // If we can pointerify, ADDIW will be enough. + // If we can pointerify, SEXT_W will be enough. if (!jo_->enablePointerify) emit_->SUB(mr[mipsReg].reg, mr[mipsReg].reg, MEMBASEREG); - emit_->ADDIW(mr[mipsReg].reg, mr[mipsReg].reg, 0); + emit_->SEXT_W(mr[mipsReg].reg, mr[mipsReg].reg); mr[mipsReg].loc = MIPSLoc::RVREG; ar[mr[mipsReg].reg].normalized32 = true; ar[mr[mipsReg].reg].pointerified = false; } else if (!jo_->enablePointerify) { emit_->SUB(destReg, mr[mipsReg].reg, MEMBASEREG); - emit_->ADDIW(destReg, destReg, 0); + emit_->SEXT_W(destReg, destReg); } else { - emit_->ADDIW(destReg, mr[mipsReg].reg, 0); + emit_->SEXT_W(destReg, mr[mipsReg].reg); } - ar[mr[mipsReg].reg].pointerified = false; break; } @@ -471,7 +472,6 @@ RiscVReg RiscVRegCache::MapReg(IRRegIndex mipsReg, MIPSMap mapFlags) { _dbg_assert_(!ar[riscvReg].isDirty && (mapFlags & MIPSMap::DIRTY) != MIPSMap::DIRTY); #endif emit_->SUB(riscvReg, riscvReg, MEMBASEREG); - ar[riscvReg].normalized32 = false; } mr[mipsReg].loc = MIPSLoc::RVREG; ar[riscvReg].normalized32 = false; From 3ae520c35df65f92ab3c738d30c98921153ff9be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 24 Jul 2023 12:00:16 +0200 Subject: [PATCH 091/167] RetroAchievements: Fix another race condition, improve logging. Seems to help the frontend problem. --- Core/RetroAchievements.cpp | 11 ++++++++--- UI/EmuScreen.cpp | 11 +++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index c02c6cd444..4c3ea97710 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -89,7 +89,7 @@ rc_client_t *GetClient() { } bool IsLoggedIn() { - return rc_client_get_user_info(g_rcClient) != nullptr; + return rc_client_get_user_info(g_rcClient) != nullptr && !g_isLoggingIn; } bool EncoreModeActive() { @@ -129,7 +129,11 @@ bool WarnUserIfChallengeModeActive(const char *message) { } bool IsBlockingExecution() { - return g_isIdentifying || g_isLoggingIn; + if (g_isLoggingIn || g_isIdentifying) { + // Useful for debugging race conditions. + // INFO_LOG(ACHIEVEMENTS, "isLoggingIn: %d isIdentifying: %d", (int)g_isLoggingIn, (int)g_isIdentifying); + } + return g_isLoggingIn || g_isIdentifying; } static u32 GetGameID() { @@ -313,6 +317,7 @@ static void event_handler_callback(const rc_client_event_t *event, rc_client_t * static void login_token_callback(int result, const char *error_message, rc_client_t *client, void *userdata) { switch (result) { case RC_OK: + INFO_LOG(ACHIEVEMENTS, "Successful login by token."); OnAchievementsLoginStateChange(); break; case RC_NO_RESPONSE: @@ -328,7 +333,7 @@ static void login_token_callback(int result, const char *error_message, rc_clien default: { auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS); - ERROR_LOG(ACHIEVEMENTS, "Failure logging in via token: %d, %s", result, error_message); + ERROR_LOG(ACHIEVEMENTS, "Callback: Failure logging in via token: %d, %s", result, error_message); g_OSD.Show(OSDType::MESSAGE_WARNING, ac->T("Failed logging in to RetroAchievements")); OnAchievementsLoginStateChange(); break; diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index d32335c663..90aa75199b 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -243,14 +243,14 @@ void EmuScreen::bootGame(const Path &filename) { } if (PSP_IsIniting()) { - std::string error_string; + std::string error_string = "(unknown error)"; bootPending_ = !PSP_InitUpdate(&error_string); if (!bootPending_) { invalid_ = !PSP_IsInited(); if (invalid_) { errorMessage_ = error_string; - ERROR_LOG(BOOT, "%s", errorMessage_.c_str()); + ERROR_LOG(BOOT, "isIniting bootGame error: %s", errorMessage_.c_str()); return; } bootComplete(); @@ -331,7 +331,7 @@ void EmuScreen::bootGame(const Path &filename) { bootPending_ = false; invalid_ = true; errorMessage_ = error_string; - ERROR_LOG(BOOT, "%s", errorMessage_.c_str()); + ERROR_LOG(BOOT, "InitStart bootGame error: %s", errorMessage_.c_str()); } if (PSP_CoreParameter().compat.flags().RequireBufferedRendering && g_Config.bSkipBufferEffects) { @@ -360,8 +360,9 @@ void EmuScreen::bootComplete() { System_Notify(SystemNotification::BOOT_DONE); System_Notify(SystemNotification::DISASSEMBLY); - NOTICE_LOG(BOOT, "Loading %s...", PSP_CoreParameter().fileToStart.c_str()); + NOTICE_LOG(BOOT, "Booted %s...", PSP_CoreParameter().fileToStart.c_str()); if (!Achievements::ChallengeModeActive()) { + // Don't auto-load savestates in challenge mode. autoLoad(); } @@ -1075,6 +1076,8 @@ void EmuScreen::update() { } if (bootPending_) { + // Keep trying the boot until bootPending_ is lifted. + // It may be delayed due to RetroAchievements or any other cause. bootGame(gamePath_); } From a72c4aa38396db53196124dae2c8f0d45a5d6074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 24 Jul 2023 12:08:15 +0200 Subject: [PATCH 092/167] Actually fix the race condition. Can't do any initialization step while waiting. --- Core/System.cpp | 10 ++++------ UI/EmuScreen.cpp | 6 ++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Core/System.cpp b/Core/System.cpp index 6a1354ef82..2433b6288e 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -477,11 +477,6 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { } bool PSP_InitUpdate(std::string *error_string) { - if (Achievements::IsBlockingExecution()) { - // Keep waiting. - return false; - } - if (pspIsInited || !pspIsIniting) { return true; } @@ -491,7 +486,10 @@ bool PSP_InitUpdate(std::string *error_string) { } bool success = !g_CoreParameter.fileToStart.empty(); - *error_string = g_CoreParameter.errorString; + if (!g_CoreParameter.errorString.empty()) { + *error_string = g_CoreParameter.errorString; + } + if (success && gpu == nullptr) { PSP_SetLoading("Starting graphics..."); Draw::DrawContext *draw = g_CoreParameter.graphicsContext ? g_CoreParameter.graphicsContext->GetDrawContext() : nullptr; diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 90aa75199b..aeeea0be4c 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -233,6 +233,11 @@ bool EmuScreen::bootAllowStorage(const Path &filename) { void EmuScreen::bootGame(const Path &filename) { auto sc = GetI18NCategory(I18NCat::SCREEN); + if (Achievements::IsBlockingExecution()) { + // Keep waiting. + return; + } + if (PSP_IsRebooting()) return; if (PSP_IsInited()) { @@ -244,6 +249,7 @@ void EmuScreen::bootGame(const Path &filename) { if (PSP_IsIniting()) { std::string error_string = "(unknown error)"; + bootPending_ = !PSP_InitUpdate(&error_string); if (!bootPending_) { From 2baba83f1a8f4576691dbd89090821821b2cd483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 24 Jul 2023 14:39:40 +0200 Subject: [PATCH 093/167] Add a bunch of new icon images to the atlas. --- Common/System/OSD.h | 1 + UI/OnScreenDisplay.cpp | 4 +- assets/ui_atlas.meta | Bin 1477 -> 1633 bytes assets/ui_atlas.zim | Bin 67767 -> 72399 bytes source_assets/image/buttons.svg | 341 +++++++++++++++++++++++++-- source_assets/image/fast_forward.png | Bin 0 -> 936 bytes source_assets/image/pausepng.png | Bin 0 -> 396 bytes source_assets/image/play.png | Bin 0 -> 751 bytes source_assets/image/record.png | Bin 0 -> 1123 bytes source_assets/image/speaker.png | Bin 0 -> 825 bytes source_assets/image/speaker_max.png | Bin 0 -> 1988 bytes source_assets/image/speaker_off.png | Bin 0 -> 1330 bytes source_assets/image/stop.png | Bin 0 -> 315 bytes source_assets/image/winner_cup.png | Bin 0 -> 1251 bytes ui_atlasscript.txt | 8 + 15 files changed, 337 insertions(+), 17 deletions(-) create mode 100644 source_assets/image/fast_forward.png create mode 100644 source_assets/image/pausepng.png create mode 100644 source_assets/image/play.png create mode 100644 source_assets/image/record.png create mode 100644 source_assets/image/speaker.png create mode 100644 source_assets/image/speaker_max.png create mode 100644 source_assets/image/speaker_off.png create mode 100644 source_assets/image/stop.png create mode 100644 source_assets/image/winner_cup.png diff --git a/Common/System/OSD.h b/Common/System/OSD.h index 6432cca01c..823236469d 100644 --- a/Common/System/OSD.h +++ b/Common/System/OSD.h @@ -19,6 +19,7 @@ enum class OSDType { // Side entries ACHIEVEMENT_PROGRESS, // Achievement icon + "measured_progress" text, auto-hide after 2s ACHIEVEMENT_CHALLENGE_INDICATOR, // Achievement icon ONLY, no auto-hide + LEADERBOARD_TRACKER, }; diff --git a/UI/OnScreenDisplay.cpp b/UI/OnScreenDisplay.cpp index 7d5b2a0ee0..a8bc84c441 100644 --- a/UI/OnScreenDisplay.cpp +++ b/UI/OnScreenDisplay.cpp @@ -202,7 +202,7 @@ static void RenderOSDProgressBar(UIContext &dc, const OnScreenDisplay::ProgressB static void MeasureLeaderboardTracker(UIContext &dc, const std::string &text, float *width, float *height) { dc.MeasureText(dc.GetFontStyle(), 1.0f, 1.0f, text.c_str(), width, height); - *width += 10.0f; + *width += 16.0f; *height += 10.0f; } @@ -214,7 +214,7 @@ static void RenderLeaderboardTracker(UIContext &dc, const Bounds &bounds, const dc.FillRect(background, bounds); dc.SetFontStyle(dc.theme->uiFont); dc.SetFontScale(1.0f, 1.0f); - dc.DrawTextShadowRect(text.c_str(), bounds.Inset(5.0f, 5.0f), colorAlpha(0xFFFFFFFF, alpha), ALIGN_VCENTER | ALIGN_LEFT); + dc.DrawTextShadowRect(text.c_str(), bounds.Inset(5.0f, 5.0f), colorAlpha(0xFFFFFFFF, alpha), ALIGN_VCENTER | ALIGN_HCENTER); } void OnScreenMessagesView::Draw(UIContext &dc) { diff --git a/assets/ui_atlas.meta b/assets/ui_atlas.meta index f2df6bfd1d18f1fb91a895a09e410be04b72d5ee..b969735e1c64424ba92524bd8475f089cf167f22 100644 GIT binary patch literal 1633 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 diff --git a/assets/ui_atlas.zim b/assets/ui_atlas.zim index 3ab85b7e220418281cd2eee94a157f01d6e2cf8c..d47766ad492a2632289e94ebb426947a74fb9e62 100644 GIT binary patch delta 54491 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�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 M*TB|`(yQt~2(nyU9RL6T delta 49822 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 mgb*KBYef43# + x="-0.17000000000000001" + y="-0.13999999999999999" + width="1.3600000000000001" + height="1.28"> + inkscape:label="Drop Shadow" + width="1.7200000000000004" + height="1.2" + x="-0.23000000000000009" + y="-0.10000000000000001"> + + + + + + + + + + + + + + + + + + + + + + x="-3.2951229" + y="-3.9247968" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source_assets/image/fast_forward.png b/source_assets/image/fast_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..d7fb6ae6b41d73eccce422e360541a79279ba9a1 GIT binary patch literal 936 zcmV;Z16TZsP)YTsmT_ChHPPx)P4dla*>PC zL}P-)lO{-v9u&N31a?>C0O!2)1OPCarUiR@duP_y*KgzB%)Eptm&?JvzP|a@)zwiiJplkPO_Ko- z7#bS-m`opcaMsqID<9|O??KiEc>e6Lc2MTS}L?42v=Yu zbW>;Qg#PUybW>+&gmwi(h@;T1U3vl|;5H+;dgBc5RtMVTEO9Q+Ev^aGH~<)$N%$PWO-6HKX8N^u-F z51oN- z*x$a?TU*!lf*=SHyB=B?Lz1L>06HBeO9f+^=3zRWzGc@#y-bRt%*A4{F93AfF?Px2 zax*;7f4A$Qf+>|s%N)nep*6z3cj{>hQ>|7ri;Iho+9hm#9Gx)?Lro@=Jm_v|M`v_h z-*pm}7RHby={}r=rGhyUc8sRSahViFnUBR{3A9GoZ!|r&Fb4+*%N)ne!oRRWBVn7u zRIAlL3kwUa6815g2wR_HXIu(fpJQiqUEfV45>a^h(yEr+qAXP8FD1G)j8z}|`Mov!` z$B>BDw=-<>njHjM?@u@y9jJKIcS_%}Q@NL(>+EyPo3Qu^SEiD@!ji*xxfV`MPxt|Fv7s zKZdvrzIyg<=hgy z>!h=(-ss-%#|AvhuP7)bDHUz>x&QtCz2EDzzZomG%&%{YIQc9q^beErD_;wSd;3{| PA;;k9>gTe~DWM4fe#)jt literal 0 HcmV?d00001 diff --git a/source_assets/image/play.png b/source_assets/image/play.png new file mode 100644 index 0000000000000000000000000000000000000000..d324c25b844c2752c0831a657f661c4290295f3b GIT binary patch literal 751 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 DRh4uv literal 0 HcmV?d00001 diff --git a/source_assets/image/speaker_max.png b/source_assets/image/speaker_max.png new file mode 100644 index 0000000000000000000000000000000000000000..8625b2b2ee002745985db1003c0f50a74503e017 GIT binary patch literal 1988 zcmV;#2RrzQP)z-}mF*`yTK)Bil2uDF6^x`*YPl z9;47+Q&ZzxSXg*p)3jf#Q`RvuGP38?sZ-t!+JA^d0M7I0&+kxG_1{E9ilTI^LtaWI zlOHg~^jIwRYXD9Y|fNwO@TS%)6`+}zv;L_~&Rs8_CBslx`+ zMgSlI@J1q$cE(tqEF{Zv@G-KYxdZ@WV`F2WP$>MDh$xv%{t}S@|~TXFQLLqb}YqmrRC}D?A(>l z=X)&}ST>uzV*&Kb)zHk&&bBbdG)>d)*3{JaxVP%->o*k&g-If!+1c6O0B~>_DISlv z7mGz3W9*USVvM=AZQJ&?BuQJ$dv#=FWx>a;KoqKwE zdYbF%>iz;izjyCmK9x#+Qd?X5ejpGy0pLRbIsn2-VOWU@g~ESs+_C&ZF7-KpSQGb8`8>V3afG3yB-60}MBoZH)GRujE>FMc@ z6-5b2lEgSq6h&f;3>hX9Qyk74&!7GvPbYNiMhXA}LQio;PQ7&5nfIS=z zFMBAJO8uONM^)9EL_~_B{1bqiKR-S`ew>KNFbw6$kt5HW`lP|Z!S9&-d_zOS5}zv` z9v*Ho0a%Um#fbxIUICE3zrX(tA|l3E(NfOh(W6JJ8Dp9yUu*!xlP6DZ;o-b}`}Vg0 z*eU_pvG%K+_o5$J0G;L$uCA{37Yc<5BBE$C`ZJ3@Pd=X?BqExcntIRNxB$STXkA7Dp8K#D2g%w03MI$Yo=iWz;e0VeE_iA?Jt`01b~XgVhjKr4o58jVMBlfL0B!U zB+K#y07#NlV*yUOuFC*mv)MLT`K3}RWAdvu45$)>^;%U`83064e9me(m&>J1!&W{R zV|nw^1#EmkS}9~O4LSjUi1de06B+!GwG=-!AR@8?5~`}I0sz1mOIyGSUavO*0ES`6 z7CuB#^qS8vZWz!SIV1)GfmZ>bSS(Hf;BS4w<#KUA9H;<5u-omMO@0;t{XcU^0ATa` z{d)l*m&*;9fCK=d%jMbu0J^TntuhXW;|nH#8URUnm@41QD;b8(R!#%PmRp|?3TyFwttzj!ohH`l z-@o4nfGxKURaN`4+3fYor6|hZy1Kev#ZuE10XW9T$4_v%L?ZDv0CaS81aw`WsT?N& zNW9e;hLLY-YT9h-kwT%+4@`c#y1LpAYja(TbX}h@hcg!d*Vx$Do0Whfk;vB#!$=s0 zAz!#~VH=jpTL9qBX0z9ch%%YXUr`?Fov~Q#Ln5Mug@s$-)4`JfA|fi4N^(z6&tU*n z;c&Rs0;rvbtgEZ*OVMcbyC^qR+r^6)Ut)|Eh=_)UhQ5#Tfac{5Hy)4w7Uo3?;)ePxp^AEw3$}~ zz~0i*a#|1shpy|1?p zCRvt0St*orb8|mgO?ZBOzJnXxwQHB(+`M@F`0*D?rBafJXkudGG?qgA#Kcb%zE~{w zyY&D9;GCYGelrq@97cJ5cSfVpUlA=;S1=gdf(>+x<>U0^c*;*Q{JifT z8XEc@V=S9ar(4$pD4kCKo`{Tzi3w|xKQj^mI76Y(YumSPf8?vjvm);0&jpyr(*FR{ WY(6AmpwjpN000000004b3#c}2nYxW zdO<~PstncwrwGs6H67Yu+b0J*_n@VcTX|0EKL4<53Q zLk|XkvA({(FdmQpK-5@SS^4NFK?VS(?(Xg<*4NiPgo;=!b|f$c01F^zbaeE^&CN}o zq(m$h`!Ev&XQ>0H3l@MW6bjuaE-rqbVHg$wWLb{0EPEmoplw)NTifY$C(UNFrJE>!vGBPsa1YiYl3_uRm%-DKJ7!Ba@ zc($i98jZdOfSsJ2JSWStKnU3k1OjiW*XiNdrlzLCL?ZDAA%sYhbl2^6A6NU{4V#{x zeg%M0?TpcB+m)G|oO~$-mKBXg+o>hF83+ViIyknesi{zsq~EnfS(f7-kLS#;o6r(C zj=KmTdzXA?=)qdmPIJQH@E0jKx^k?;;m8+7@g9X*85$aTR(IS3!d|9e@2clC+?O=Y z=kr|!aIB`Lra%d|NF>rh<2a7HCJ4fBI+fB# z9TdiK91U})?UnuNzAKNPwMM*s*^E|&+Qc`j)91h<~$xRFl4AfOt zRt^H#z#eULgTU2U>hEcP82F^svWsOU6yKIw-BV+bL8Zk`T@!`9i^*`1e{ zcNPE$A+ic%OG`_?o|~I<;{MijXs{hY2%#&+*4EaZ=;-M9J~ub_8~`YavNkw4_$try z-)Xp#l9F%ye*a}0^5Qpoy>b1-lNw;B-1|qA31Kp`_8#(Hd;MHyNl;Ycv|YrR6A!vN1nD ze_1VvK5SxQq7GZ{Ns|iHT6PN7$Z_0jDX^?aB=T_z>Uj#Kg@(IL;a2fpkJ}QLXxDu#>U1|Y6m8c<1SIK7PTvb+wDH?^ZA~_gV(B|va-^$xVZRq z+T8uX2q9#3b#z5Ou&xid2}^*XRajjW}mrFYVuR20Q*x7%A-mVLxxv7BjcZob2^ zY~9-0S{8k8nScRXYaw0B>FMctGMP;NsZ}VMO#Zd7u(18@6%L2r&J;}Vqfl!G0H&_4 zuE*Ec*Jr7jRsIXue&EKImX>1r%aejN9~Df)Wjh>>Q-UD;k^+0{C}G>Uva&K8y+QsT oV8H#~P}X2D_=yU;nu&q_1;>AQqBIW&HUIzs07*qoM6N<$g8L0+d;kCd literal 0 HcmV?d00001 diff --git a/source_assets/image/stop.png b/source_assets/image/stop.png new file mode 100644 index 0000000000000000000000000000000000000000..3860c5b38b350a3654ff5aa43ec63d6206845538 GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^svyk41|*NpQ(y*CEX7WqAsj$Z!;#Vf4nJ zh_r$*V^)yQ2B4s1iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0vJJY5_^ zB3j?xFyuO9AaV5LbQg`AcdJ;}KajMUTwd~)ch1psaig{MKZ(5MsLOH`@#tkJabP0l+XkKN2qq0 literal 0 HcmV?d00001 diff --git a/source_assets/image/winner_cup.png b/source_assets/image/winner_cup.png new file mode 100644 index 0000000000000000000000000000000000000000..27ba1ed945d4fc1ed82bcf6b8ffd9ce2deb12ec7 GIT binary patch literal 1251 zcmV<91RVQ`P)pur00004b3#c}2nYxW zd4Ox+hbk;WS@yL^KctCPS|tsxniMT2VvR%_GbV8| znVb1IeHc@s=F7~qPy2_E%$=J%zd85ZbI!fMBh@Fajy}Tar=Gs`$N-=Lph_K@Y7D?f zB9Z5MdV22ay3X4{L_~3q$MZ1&wO%L=0FM&uY;0_BeSLjz*Z~9pk|g~CpcOy?rnPj` zvYg-Ve?FZ~KVXbS8Dl%g$HzYskwG>|QIy?$J|AI>{UyutS|X9SJ2W)ZjtcI&xOO`4^pYrw{`-ks+!#0-Ti!XbMq5bRTD%+ znx-WIxXMCpZEd|>{PWDrOh14Y0By-+(l)Tg#l_nIoB-U3L}KC;G*Jpwx}uCgAn+^9 zC5!(4{_Cf|3yiUyN=|rLMIwuD+aV&7BzPdE2O=U> zRrjZ-r=P)DH!3R*fHRp)P8A1JRn?X-VHie1)3g&JB3;*W%gf8Rs!iJku2?9Bs+tG}gRj91mJKC&4nRvF5V)o5 zde$mXCX@MobaeC@%qg=}b#MTjqA1?C3bnGbavPWMp;{6`q0lQp8cH~>ux3kxq(ncZ_Pm;1f5v-1jU@Y#A{1OQII-~S?GELJJS zh@yCJczF16n8h3qo1!`gGq|?E%v~;@&v$igZS74_6z>~`QK*&s7-R8xJpRSp+}sTS zZkW3kX9?(~CB)ha6r0cpQ2tt7|CZoccR5j--9-tMS)DB0blJBYqWsg*k{sG*{^kf-KhNJ)h N002ovPDHLkV1n2?L=yl2 literal 0 HcmV?d00001 diff --git a/ui_atlasscript.txt b/ui_atlasscript.txt index c2f633f3ad..62a8037c7a 100644 --- a/ui_atlasscript.txt +++ b/ui_atlasscript.txt @@ -72,3 +72,11 @@ image I_THREE_DOTS source_assets/image/three_dots.png copy image I_INFO source_assets/image/info.png copy image I_RETROACHIEVEMENTS_LOGO source_assets/image/retroachievements_logo.png copy image I_CHECKMARK source_assets/image/checkmark.png copy +image I_PLAY source_assets/image/play.png copy +image I_STOP source_assets/image/stop.png copy +image I_FASTFORWARD source_assets/image/fast_forward.png copy +image I_RECORD source_assets/image/record.png copy +image I_SPEAKER source_assets/image/speaker.png copy +image I_SPEAKER_MAX source_assets/image/speaker_max.png copy +image I_SPEAKER_OFF source_assets/image/speaker_off.png copy +image I_WINNER_CUP source_assets/image/winner_cup.png copy From fecd0b8cd7c74ead6b8430d02b52208e6f663941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 24 Jul 2023 14:56:35 +0200 Subject: [PATCH 094/167] Use the new play button icon in the retroachievement sound customizer --- UI/RetroAchievementScreens.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UI/RetroAchievementScreens.cpp b/UI/RetroAchievementScreens.cpp index a181bbe7aa..8ab19214ed 100644 --- a/UI/RetroAchievementScreens.cpp +++ b/UI/RetroAchievementScreens.cpp @@ -34,6 +34,10 @@ AudioFileChooser::AudioFileChooser(std::string *value, const std::string &title, layoutParams_->width = FILL_PARENT; layoutParams_->height = ITEM_HEIGHT; } + Add(new Choice(ImageID("I_PLAY"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) { + g_BackgroundAudio.SFX().Play(sound_, 0.6f); + return UI::EVENT_DONE; + }); Add(new FileChooserChoice(value, title, BrowseFileType::SOUND_EFFECT, new LinearLayoutParams(1.0f)))->OnChange.Add([=](UI::EventParams &e) { // TODO: Check the file format here. // Need to forward the event out. @@ -50,10 +54,6 @@ AudioFileChooser::AudioFileChooser(std::string *value, const std::string &title, } return UI::EVENT_DONE; }); - Add(new Choice(ImageID("I_ARROW_RIGHT"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) { - g_BackgroundAudio.SFX().Play(sound_, 0.6f); - return UI::EVENT_DONE; - }); Add(new Choice(ImageID("I_TRASHCAN"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) { g_BackgroundAudio.SFX().UpdateSample(sound, nullptr); value->clear(); From 2b826b56147f90c28da8004742cb02abda65537a Mon Sep 17 00:00:00 2001 From: Hoe Hao Cheng Date: Tue, 25 Jul 2023 12:55:23 +0800 Subject: [PATCH 095/167] SDL: implement TextDrawer using SDL2_ttf --- CMakeLists.txt | 8 + Common/Render/Text/draw_text.cpp | 3 + Common/Render/Text/draw_text_sdl.cpp | 263 +++++++++++++++++++++++++++ Common/Render/Text/draw_text_sdl.h | 33 ++++ 4 files changed, 307 insertions(+) create mode 100644 Common/Render/Text/draw_text_sdl.cpp create mode 100644 Common/Render/Text/draw_text_sdl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d2e98ac9c..f34361dd52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -258,11 +258,13 @@ endif() if(NOT LIBRETRO AND NOT IOS AND NOT MACOSX) find_package(SDL2) + find_package(SDL2_TTF) endif() if(MACOSX AND NOT IOS) if(USE_SYSTEM_LIBSDL2) find_package(SDL2) + find_package(SDL2_TTF) else() find_library(SDL2Fwk SDL2 REQUIRED PATHS SDL/macOS) message(STATUS "found SDL2Fwk=${SDL2Fwk}") @@ -1317,6 +1319,10 @@ else() SDL/SDLVulkanGraphicsContext.cpp ) endif() + if(SDL2_TTF_FOUND) + add_definitions(-DUSE_SDL2_TTF) + set(nativeExtraLibs ${nativeExtraLibs} SDL2_ttf::SDL2_ttf) + endif() if(APPLE) set(nativeExtra ${nativeExtra} SDL/SDLMain.h @@ -1458,6 +1464,8 @@ add_library(native STATIC ${nativeExtra} Common/Render/Text/draw_text_qt.cpp Common/Render/Text/draw_text_qt.h + Common/Render/Text/draw_text_sdl.cpp + Common/Render/Text/draw_text_sdl.h ext/jpge/jpgd.cpp ext/jpge/jpgd.h ext/jpge/jpge.cpp diff --git a/Common/Render/Text/draw_text.cpp b/Common/Render/Text/draw_text.cpp index 4d4b58e7f9..cb09dbda98 100644 --- a/Common/Render/Text/draw_text.cpp +++ b/Common/Render/Text/draw_text.cpp @@ -11,6 +11,7 @@ #include "Common/Render/Text/draw_text_uwp.h" #include "Common/Render/Text/draw_text_qt.h" #include "Common/Render/Text/draw_text_android.h" +#include "Common/Render/Text/draw_text_sdl.h" TextDrawer::TextDrawer(Draw::DrawContext *draw) : draw_(draw) { // These probably shouldn't be state. @@ -92,6 +93,8 @@ TextDrawer *TextDrawer::Create(Draw::DrawContext *draw) { drawer = new TextDrawerQt(draw); #elif PPSSPP_PLATFORM(ANDROID) drawer = new TextDrawerAndroid(draw); +#elif USE_SDL2_TTF + drawer = new TextDrawerSDL(draw); #endif if (drawer && !drawer->IsReady()) { delete drawer; diff --git a/Common/Render/Text/draw_text_sdl.cpp b/Common/Render/Text/draw_text_sdl.cpp new file mode 100644 index 0000000000..9e228ebef7 --- /dev/null +++ b/Common/Render/Text/draw_text_sdl.cpp @@ -0,0 +1,263 @@ +#include "ppsspp_config.h" + +#include +#include + +#include "Common/Log.h" +#include "Common/System/Display.h" +#include "Common/GPU/thin3d.h" +#include "Common/Data/Hash/Hash.h" +#include "Common/Data/Text/WrapText.h" +#include "Common/Data/Encoding/Utf8.h" +#include "Common/File/VFS/VFS.h" +#include "Common/Render/Text/draw_text.h" +#include "Common/Render/Text/draw_text_sdl.h" + +#if defined(USE_SDL2_TTF) + +#include "SDL2/SDL.h" +#include "SDL2/SDL_ttf.h" + +const uint32_t MAX_WIDTH = 4096; + +TextDrawerSDL::TextDrawerSDL(Draw::DrawContext *draw): TextDrawer(draw) { + if (TTF_Init() < 0) { + ERROR_LOG(G3D, "Unable to initialize SDL2_ttf"); + } + + dpiScale_ = CalculateDPIScale(); +} + +TextDrawerSDL::~TextDrawerSDL() { + ClearCache(); + TTF_Quit(); +} + +uint32_t TextDrawerSDL::SetFont(const char *fontName, int size, int flags) { + uint32_t fontHash = fontName && strlen(fontName) ? hash::Adler32((const uint8_t *)fontName, strlen(fontName)) : 0; + fontHash ^= size; + fontHash ^= flags << 10; + + auto iter = fontMap_.find(fontHash); + if (iter != fontMap_.end()) { + fontHash_ = fontHash; + return fontHash; + } + + const char *useFont = fontName ? fontName : "Roboto-Condensed.ttf"; + const int ptSize = (int)((size + 6) / dpiScale_); + + TTF_Font *font = TTF_OpenFont(useFont, ptSize); + + if (!font) { + File::FileInfo fileInfo; + g_VFS.GetFileInfo("Roboto-Condensed.ttf", &fileInfo); + font = TTF_OpenFont(fileInfo.fullName.c_str(), ptSize); + } + + fontMap_[fontHash] = font; + fontHash_ = fontHash; + return fontHash; +} + +void TextDrawerSDL::SetFont(uint32_t fontHandle) { + uint32_t fontHash = fontHandle; + auto iter = fontMap_.find(fontHash); + if (iter != fontMap_.end()) { + fontHash_ = fontHandle; + } else { + ERROR_LOG(G3D, "Invalid font handle %08x", fontHandle); + } +} + +void TextDrawerSDL::MeasureString(const char *str, size_t len, float *w, float *h) { + CacheKey key{ std::string(str, len), fontHash_ }; + + TextMeasureEntry *entry; + auto iter = sizeCache_.find(key); + if (iter != sizeCache_.end()) { + entry = iter->second.get(); + } else { + TTF_Font *font = fontMap_.find(fontHash_)->second; + + int width = 0; + int height = 0; + TTF_SizeUTF8(font, str, &width, &height); + + entry = new TextMeasureEntry(); + entry->width = width; + entry->height = height; + sizeCache_[key] = std::unique_ptr(entry); + } + + entry->lastUsedFrame = frameCount_; + *w = entry->width * fontScaleX_ * dpiScale_; + *h = entry->height * fontScaleY_ * dpiScale_; +} + +void TextDrawerSDL::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) { + std::string toMeasure = std::string(str, len); + int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT); + if (wrap) { + bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; + WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w, wrap); + } + + TTF_Font* font = fontMap_.find(fontHash_)->second; + + int width = 0; + int height = 0; + TTF_SizeUTF8(font, str, &width, &height); + + *w = width * fontScaleX_ * dpiScale_; + *h = height * fontScaleY_ * dpiScale_; +} + +void TextDrawerSDL::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { + using namespace Draw; + if (!strlen(str)) + return; + + CacheKey key{ std::string(str), fontHash_ }; + target.Flush(true); + + TextStringEntry *entry; + + auto iter = cache_.find(key); + if (iter != cache_.end()) { + entry = iter->second.get(); + entry->lastUsedFrame = frameCount_; + } else { + DataFormat texFormat = Draw::DataFormat::R4G4B4A4_UNORM_PACK16; + + entry = new TextStringEntry(); + + TextureDesc desc{}; + std::vector bitmapData; + DrawStringBitmap(bitmapData, *entry, texFormat, str, align); + desc.initData.push_back(&bitmapData[0]); + + desc.type = TextureType::LINEAR2D; + desc.format = texFormat; + desc.width = entry->bmWidth; + desc.height = entry->bmHeight; + desc.depth = 1; + desc.mipLevels = 1; + desc.tag = "TextDrawer"; + entry->texture = draw_->CreateTexture(desc); + cache_[key] = std::unique_ptr(entry); + } + + if (entry->texture) { + draw_->BindTexture(0, entry->texture); + } + + float w = entry->bmWidth * fontScaleX_ * dpiScale_; + float h = entry->bmHeight * fontScaleY_ * dpiScale_; + DrawBuffer::DoAlign(align, &x, &y, &w, &h); + if (entry->texture) { + target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, 1.0f, 1.0f, color); + target.Flush(true); + } +} +void TextDrawerSDL::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align) { + if (!strlen(str)) { + bitmapData.clear(); + return; + } + + std::string processed_str(str); + processed_str = std::regex_replace(processed_str, std::regex("&&"), "&"); + + TTF_Font *font = fontMap_.find(fontHash_)->second; + + if (align & ALIGN_HCENTER) + TTF_SetFontWrappedAlign(font, TTF_WRAPPED_ALIGN_CENTER); + else if (align & ALIGN_LEFT) + TTF_SetFontWrappedAlign(font, TTF_WRAPPED_ALIGN_LEFT); + else if (align & ALIGN_RIGHT) + TTF_SetFontWrappedAlign(font, TTF_WRAPPED_ALIGN_RIGHT); + + SDL_Color fgColor = { 0xFF, 0xFF, 0xFF, 0xFF }; + SDL_Surface *text = TTF_RenderUTF8_Blended(font, processed_str.c_str(), fgColor); + SDL_LockSurface(text); + + entry.texture = nullptr; + entry.bmWidth = entry.width = text->pitch / sizeof(uint32_t); + entry.bmHeight = entry.height = text->h; + entry.lastUsedFrame = frameCount_; + + uint32_t *imageData = (uint32_t *) text->pixels; + + if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) { + bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t)); + uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0]; + + for (int x = 0; x < entry.bmWidth; x++) { + for (int y = 0; y < entry.bmHeight; y++) { + uint64_t index = entry.bmWidth * y + x; + bitmapData16[entry.bmWidth * y + x] = 0xfff0 | (imageData[index] >> 28); + } + } + } else if (texFormat == Draw::DataFormat::R8_UNORM) { + bitmapData.resize(entry.bmWidth * entry.bmHeight); + for (int x = 0; x < entry.bmWidth; x++) { + for (int y = 0; y < entry.bmHeight; y++) { + uint64_t index = text->pitch / sizeof(uint32_t) * y + x; + bitmapData[entry.bmWidth * y + x] = imageData[index] >> 24; + } + } + } else { + _assert_msg_(false, "Bad TextDrawer format"); + } + + SDL_UnlockSurface(text); + SDL_FreeSurface(text); +} + +void TextDrawerSDL::OncePerFrame() { + // Reset everything if DPI changes + float newDpiScale = CalculateDPIScale(); + if (newDpiScale != dpiScale_) { + dpiScale_ = newDpiScale; + ClearCache(); + } + + // Drop old strings. Use a prime number to reduce clashing with other rhythms + if (frameCount_ % 23 == 0) { + for (auto iter = cache_.begin(); iter != cache_.end();) { + if (frameCount_ - iter->second->lastUsedFrame > 100) { + if (iter->second->texture) + iter->second->texture->Release(); + cache_.erase(iter++); + } else { + iter++; + } + } + + for (auto iter = sizeCache_.begin(); iter != sizeCache_.end(); ) { + if (frameCount_ - iter->second->lastUsedFrame > 100) { + sizeCache_.erase(iter++); + } else { + iter++; + } + } + } +} + +void TextDrawerSDL::ClearCache() { + for (auto &iter : cache_) { + if (iter.second->texture) + iter.second->texture->Release(); + } + cache_.clear(); + sizeCache_.clear(); + + for (auto iter : fontMap_) { + TTF_CloseFont(iter.second); + } + fontMap_.clear(); + fontHash_ = 0; +} + +#endif diff --git a/Common/Render/Text/draw_text_sdl.h b/Common/Render/Text/draw_text_sdl.h new file mode 100644 index 0000000000..b86c96acb6 --- /dev/null +++ b/Common/Render/Text/draw_text_sdl.h @@ -0,0 +1,33 @@ +#pragma once + +#include "ppsspp_config.h" + +#include +#include "Common/Render/Text/draw_text.h" + +// SDL2_ttf's TTF_Font is a typedef of _TTF_Font. +struct _TTF_Font; + +class TextDrawerSDL : public TextDrawer { +public: + TextDrawerSDL(Draw::DrawContext *draw); + ~TextDrawerSDL(); + + uint32_t SetFont(const char *fontName, int size, int flags) override; + void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once. + void MeasureString(const char *str, size_t len, float *w, float *h) override; + void MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; + void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; + void DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align = ALIGN_TOPLEFT) override; + // Use for housekeeping like throwing out old strings. + void OncePerFrame() override; + +protected: + void ClearCache() override; + + uint32_t fontHash_; + std::map fontMap_; + + std::map> cache_; + std::map> sizeCache_; +}; From b97b7f366361dbc03d53dd96afe43d9b2552d217 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 23:03:59 -0700 Subject: [PATCH 096/167] riscv: Make some regcache methods private. --- Core/MIPS/RiscV/RiscVRegCache.cpp | 8 -------- Core/MIPS/RiscV/RiscVRegCache.h | 6 ++---- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index 6ddf909977..6c966dabe7 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -958,14 +958,6 @@ bool RiscVRegCache::IsImm(IRRegIndex r) const { return mr[r].loc == MIPSLoc::IMM || mr[r].loc == MIPSLoc::RVREG_IMM; } -bool RiscVRegCache::IsPureImm(IRRegIndex r) const { - _dbg_assert_(IsValidReg(r)); - if (r == MIPS_REG_ZERO) - return true; - else - return mr[r].loc == MIPSLoc::IMM; -} - u64 RiscVRegCache::GetImm(IRRegIndex r) const { _dbg_assert_(IsValidReg(r)); if (r == MIPS_REG_ZERO) diff --git a/Core/MIPS/RiscV/RiscVRegCache.h b/Core/MIPS/RiscV/RiscVRegCache.h index 3984748318..b3a8835152 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.h +++ b/Core/MIPS/RiscV/RiscVRegCache.h @@ -116,10 +116,7 @@ public: void SetImm(IRRegIndex reg, u64 immVal); bool IsImm(IRRegIndex reg) const; - bool IsPureImm(IRRegIndex reg) const; u64 GetImm(IRRegIndex reg) const; - // Optimally set a register to an imm value (possibly using another register.) - void SetRegImm(RiscVGen::RiscVReg reg, u64 imm); // May fail and return INVALID_REG if it needs flushing. RiscVGen::RiscVReg TryMapTempImm(IRRegIndex); @@ -144,7 +141,6 @@ public: void MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD); void MapDirtyDirtyIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD); void MapDirtyDirtyInIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex rs, IRRegIndex rt, RiscVJitConstants::MapType type = RiscVJitConstants::MapType::AVOID_LOAD); - void FlushRiscVReg(RiscVGen::RiscVReg r); void FlushBeforeCall(); void FlushAll(); void FlushR(IRRegIndex r); @@ -171,6 +167,8 @@ private: RiscVGen::RiscVReg AllocateReg(); RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered); RiscVGen::RiscVReg RiscVRegForFlush(IRRegIndex r); + void FlushRiscVReg(RiscVGen::RiscVReg r); + void SetRegImm(RiscVGen::RiscVReg reg, u64 imm); void AddMemBase(RiscVGen::RiscVReg reg); int GetMipsRegOffset(IRRegIndex r); From a8edf5fa246065104c990f403fe055224d1a026d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jul 2023 23:52:50 -0700 Subject: [PATCH 097/167] riscv: Reduce bloat in jit fallbacks. --- Core/MIPS/IR/IRCompBranch.cpp | 1 + Core/MIPS/RiscV/RiscVCompBranch.cpp | 2 +- Core/MIPS/RiscV/RiscVCompSystem.cpp | 58 ++++++++++++++++++++++++++++- Core/MIPS/RiscV/RiscVJit.cpp | 37 +++++++++--------- 4 files changed, 79 insertions(+), 19 deletions(-) diff --git a/Core/MIPS/IR/IRCompBranch.cpp b/Core/MIPS/IR/IRCompBranch.cpp index c21e2d6b31..49f0f7d5eb 100644 --- a/Core/MIPS/IR/IRCompBranch.cpp +++ b/Core/MIPS/IR/IRCompBranch.cpp @@ -455,6 +455,7 @@ void IRFrontend::Comp_Syscall(MIPSOpcode op) { } void IRFrontend::Comp_Break(MIPSOpcode op) { + ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC())); ir.Write(IROp::Break); js.compiling = false; } diff --git a/Core/MIPS/RiscV/RiscVCompBranch.cpp b/Core/MIPS/RiscV/RiscVCompBranch.cpp index c477a5fcae..c4b9c80c9a 100644 --- a/Core/MIPS/RiscV/RiscVCompBranch.cpp +++ b/Core/MIPS/RiscV/RiscVCompBranch.cpp @@ -55,7 +55,7 @@ void RiscVJit::CompIR_Exit(IRInst inst) { case IROp::ExitToPC: FlushAll(); - QuickJ(R_RA, dispatcher_); + QuickJ(R_RA, dispatcherCheckCoreState_); break; default: diff --git a/Core/MIPS/RiscV/RiscVCompSystem.cpp b/Core/MIPS/RiscV/RiscVCompSystem.cpp index 63eb555d31..fa613c6ce5 100644 --- a/Core/MIPS/RiscV/RiscVCompSystem.cpp +++ b/Core/MIPS/RiscV/RiscVCompSystem.cpp @@ -15,7 +15,12 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include "Common/Profiler/Profiler.h" +#include "Core/Core.h" +#include "Core/HLE/HLE.h" +#include "Core/HLE/ReplaceTables.h" #include "Core/MemMap.h" +#include "Core/MIPS/MIPSTables.h" #include "Core/MIPS/RiscV/RiscVJit.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" @@ -98,10 +103,61 @@ void RiscVJit::CompIR_System(IRInst inst) { switch (inst.op) { case IROp::Interpret: + // IR protects us against this being a branching instruction (well, hopefully.) + FlushAll(); + SaveStaticRegisters(); + LI(X10, (int32_t)inst.constant); + QuickCallFunction((const u8 *)MIPSGetInterpretFunc(MIPSOpcode(inst.constant))); + LoadStaticRegisters(); + break; + case IROp::Syscall: + FlushAll(); + SaveStaticRegisters(); + +#ifdef USE_PROFILER + // When profiling, we can't skip CallSyscall, since it times syscalls. + LI(X10, (int32_t)inst.constant); + QuickCallFunction(&CallSyscall); +#else + // Skip the CallSyscall where possible. + { + MIPSOpcode op(inst.constant); + void *quickFunc = GetQuickSyscallFunc(op); + if (quickFunc) { + LI(X10, (uintptr_t)GetSyscallFuncPointer(op)); + QuickCallFunction((const u8 *)quickFunc); + } else { + LI(X10, (int32_t)inst.constant); + QuickCallFunction(&CallSyscall); + } + } +#endif + + LoadStaticRegisters(); + // This is always followed by an ExitToPC, where we check coreState. + break; + case IROp::CallReplacement: + FlushAll(); + SaveStaticRegisters(); + QuickCallFunction(GetReplacementFunc(inst.constant)->replaceFunc); + LoadStaticRegisters(); + SUB(DOWNCOUNTREG, DOWNCOUNTREG, X10); + break; + case IROp::Break: - CompIR_Generic(inst); + FlushAll(); + // This doesn't naturally have restore/apply around it. + RestoreRoundingMode(true); + SaveStaticRegisters(); + MovFromPC(X10); + QuickCallFunction(&Core_Break); + LoadStaticRegisters(); + ApplyRoundingMode(true); + MovFromPC(SCRATCH1); + ADDI(SCRATCH1, SCRATCH1, 4); + QuickJ(R_RA, dispatcherPCInSCRATCH1_); break; default: diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 980e5b6347..3fcc91cef3 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -107,13 +107,6 @@ bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u return true; } -static u32 DoIRInst(uint64_t value) { - IRInst inst; - memcpy(&inst, &value, sizeof(inst)); - - return IRInterpret(currentMIPS, &inst, 1); -} - void RiscVJit::CompileIRInst(IRInst inst) { switch (inst.op) { case IROp::Nop: @@ -392,9 +385,15 @@ void RiscVJit::CompileIRInst(IRInst inst) { } } +static u32 DoIRInst(uint64_t value) { + IRInst inst; + memcpy(&inst, &value, sizeof(inst)); + + return IRInterpret(currentMIPS, &inst, 1); +} + void RiscVJit::CompIR_Generic(IRInst inst) { - // For now, we're gonna do it the slow and ugly way. - // Maybe there's a smarter way to fallback? + // If we got here, we're going the slow way. uint64_t value; memcpy(&value, &inst, sizeof(inst)); @@ -403,14 +402,18 @@ void RiscVJit::CompIR_Generic(IRInst inst) { SaveStaticRegisters(); QuickCallFunction(&DoIRInst); LoadStaticRegisters(); - // Result in X10 aka SCRATCH1. - _assert_(X10 == SCRATCH1); - if (BInRange(dispatcherPCInSCRATCH1_)) { - BNE(X10, R_ZERO, dispatcherPCInSCRATCH1_); - } else { - FixupBranch skip = BEQ(X10, R_ZERO); - QuickJ(R_RA, dispatcherPCInSCRATCH1_); - SetJumpTarget(skip); + + // We only need to check the return value if it's a potential exit. + if ((GetIRMeta(inst.op)->flags & IRFLAG_EXIT) != 0) { + // Result in X10 aka SCRATCH1. + _assert_(X10 == SCRATCH1); + if (BInRange(dispatcherPCInSCRATCH1_)) { + BNE(X10, R_ZERO, dispatcherPCInSCRATCH1_); + } else { + FixupBranch skip = BEQ(X10, R_ZERO); + QuickJ(R_RA, dispatcherPCInSCRATCH1_); + SetJumpTarget(skip); + } } } From 8f404a1961ad2730816815d0b0e3649466c58ac0 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 25 Jul 2023 19:12:27 -0700 Subject: [PATCH 098/167] softgpu: Fix minor typo. --- GPU/Software/Clipper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/Software/Clipper.cpp b/GPU/Software/Clipper.cpp index 2d96b1f24c..ad07285992 100644 --- a/GPU/Software/Clipper.cpp +++ b/GPU/Software/Clipper.cpp @@ -405,7 +405,7 @@ void ProcessTriangle(const ClipVertexData &v0, const ClipVertexData &v1, const C ClipVertexData &subv1 = *Vertices[indices[i + 1]]; ClipVertexData &subv2 = *Vertices[indices[i + 2]]; - if (subv0.OutsideRange() || subv1.OutsideRange() | subv2.OutsideRange()) + if (subv0.OutsideRange() || subv1.OutsideRange() || subv2.OutsideRange()) continue; if (gstate.getShadeMode() == GE_SHADE_FLAT) { From 94f7231e7375f6ecc485dc49fdb7687557d48e46 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 25 Jul 2023 19:37:28 -0700 Subject: [PATCH 099/167] net: Fix request cancelling. Was broken in #17737. --- Common/Net/NetBuffer.cpp | 8 ++++++-- Common/System/OSD.cpp | 4 ---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Common/Net/NetBuffer.cpp b/Common/Net/NetBuffer.cpp index 6a507977b2..09a4853963 100644 --- a/Common/Net/NetBuffer.cpp +++ b/Common/Net/NetBuffer.cpp @@ -76,11 +76,15 @@ bool Buffer::ReadAllWithProgress(int fd, int knownSize, RequestProgress *progres int total = 0; while (true) { bool ready = false; - while (!ready && (!progress || (progress->cancelled && !*progress->cancelled))) { - if (progress && progress->cancelled && *progress->cancelled) + + // If we might need to cancel, check on a timer for it to be ready. + // After this, we'll block on reading so we do this while first if we have a cancel pointer. + while (!ready && progress && progress->cancelled) { + if (*progress->cancelled) return false; ready = fd_util::WaitUntilReady(fd, CANCEL_INTERVAL, false); } + int retval = recv(fd, &buf[0], (int)buf.size(), MSG_NOSIGNAL); if (retval == 0) { return true; diff --git a/Common/System/OSD.cpp b/Common/System/OSD.cpp index ab1b900e69..1b26acd89e 100644 --- a/Common/System/OSD.cpp +++ b/Common/System/OSD.cpp @@ -235,10 +235,6 @@ void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, floa } } - if (message == "dorequest.php") { - found = found; - } - ProgressBar bar; bar.id = id; bar.message = std::move(message); From c3db3d5187330a4091f89e9b7245b57adac0f096 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 25 Jul 2023 20:30:05 -0700 Subject: [PATCH 100/167] arm64jit: When rouding unset, use nearest. The 0/default rounding mode is nearest, not toward zero. We set hasSetRounding only when fcr31 has a non-zero rounding mode or flush to zero set. --- Core/MIPS/ARM64/Arm64Asm.cpp | 2 +- Core/MIPS/ARM64/Arm64CompFPU.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64Asm.cpp b/Core/MIPS/ARM64/Arm64Asm.cpp index 9f70435a78..5a0b12ce65 100644 --- a/Core/MIPS/ARM64/Arm64Asm.cpp +++ b/Core/MIPS/ARM64/Arm64Asm.cpp @@ -312,7 +312,7 @@ void Arm64Jit::GenerateFixedCode(const JitOptions &jo) { // Leave this at the end, add more stuff above. if (enableDisasm) { - std::vector lines = DisassembleArm64(start, GetCodePtr() - start); + std::vector lines = DisassembleArm64(start, (int)(GetCodePtr() - start)); for (auto s : lines) { INFO_LOG(JIT, "%s", s.c_str()); } diff --git a/Core/MIPS/ARM64/Arm64CompFPU.cpp b/Core/MIPS/ARM64/Arm64CompFPU.cpp index c1cd969582..2756f5e8ed 100644 --- a/Core/MIPS/ARM64/Arm64CompFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompFPU.cpp @@ -292,7 +292,7 @@ void Arm64Jit::Comp_FPU2op(MIPSOpcode op) { fp.FMOV(fpr.R(fd), S0); } else { fp.FCMP(fpr.R(fs), fpr.R(fs)); - fp.FCVTS(fpr.R(fd), fpr.R(fs), ROUND_Z); + fp.FCVTS(fpr.R(fd), fpr.R(fs), ROUND_N); FixupBranch skip_nan = B(CC_VC); MOVI2R(SCRATCH1, 0x7FFFFFFF); fp.FMOV(fpr.R(fd), SCRATCH1); From 067a033dc0f3d19095a69f0ddf71fc01b372d499 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 24 Jul 2023 20:48:17 -0700 Subject: [PATCH 101/167] riscv: Add FPU regcache. --- Core/MIPS/ARM64/Arm64RegCache.cpp | 2 +- Core/MIPS/ARM64/Arm64RegCacheFPU.cpp | 3 +- Core/MIPS/ARM64/Arm64RegCacheFPU.h | 3 - Core/MIPS/RiscV/RiscVJit.cpp | 11 +- Core/MIPS/RiscV/RiscVJit.h | 2 + Core/MIPS/RiscV/RiscVRegCache.cpp | 68 +++-- Core/MIPS/RiscV/RiscVRegCache.h | 11 +- Core/MIPS/RiscV/RiscVRegCacheFPU.cpp | 372 +++++++++++++++++++++++++++ Core/MIPS/RiscV/RiscVRegCacheFPU.h | 85 ++++++ 9 files changed, 516 insertions(+), 41 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 3bfede4dbb..7dc2ba1ddf 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -180,7 +180,7 @@ void Arm64RegCache::MapRegTo(ARM64Reg reg, MIPSGPReg mipsReg, int mapFlags) { ar[reg].isDirty = (mapFlags & MAP_DIRTY) ? true : false; if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { if (mipsReg == MIPS_REG_ZERO) { - // If we get a request to load the zero register, at least we won't spend + // If we get a request to map the zero register, at least we won't spend // time on a memory access... emit_->MOVI2R(reg, 0); diff --git a/Core/MIPS/ARM64/Arm64RegCacheFPU.cpp b/Core/MIPS/ARM64/Arm64RegCacheFPU.cpp index 6a7b7ada56..6771713652 100644 --- a/Core/MIPS/ARM64/Arm64RegCacheFPU.cpp +++ b/Core/MIPS/ARM64/Arm64RegCacheFPU.cpp @@ -319,6 +319,7 @@ void Arm64RegCacheFPU::FlushR(MIPSReg r) { if (mr[r].reg == INVALID_REG) { ERROR_LOG(JIT, "FlushR: MipsReg had bad ArmReg"); } + FlushArmReg((ARM64Reg)(S0 + mr[r].reg)); break; case ML_MEM: @@ -329,8 +330,6 @@ void Arm64RegCacheFPU::FlushR(MIPSReg r) { //BAD break; } - mr[r].loc = ML_MEM; - mr[r].reg = (int)INVALID_REG; } Arm64Gen::ARM64Reg Arm64RegCacheFPU::ARM64RegForFlush(int r) { diff --git a/Core/MIPS/ARM64/Arm64RegCacheFPU.h b/Core/MIPS/ARM64/Arm64RegCacheFPU.h index 657d218611..40b7647448 100644 --- a/Core/MIPS/ARM64/Arm64RegCacheFPU.h +++ b/Core/MIPS/ARM64/Arm64RegCacheFPU.h @@ -17,8 +17,6 @@ #pragma once -#pragma once - #include "Core/MIPS/MIPS.h" #include "Core/MIPS/ARM64/Arm64RegCache.h" #include "Core/MIPS/MIPSVFPUUtils.h" @@ -165,7 +163,6 @@ private: MIPSComp::JitOptions *jo_; int numARMFpuReg_; - int qTime_; enum { // On ARM64, each of the 32 registers are full 128-bit. No sharing of components! diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 3fcc91cef3..11e98d9f59 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -26,7 +26,7 @@ namespace MIPSComp { using namespace RiscVGen; using namespace RiscVJitConstants; -RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo) { +RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo), fpr(mipsState, &jo) { // Automatically disable incompatible options. if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) { jo.enablePointerify = false; @@ -40,7 +40,7 @@ RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo) memset(blockStartAddrs_, 0, sizeof(blockStartAddrs_[0]) * MAX_ALLOWED_JIT_BLOCKS); gpr.Init(this); - // TODO: fpr + fpr.Init(this); GenerateFixedCode(jo); } @@ -79,7 +79,7 @@ bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u blockStartAddrs_[block_num] = GetCodePointer(); gpr.Start(); - // TODO: fpr. + fpr.Start(); for (const IRInst &inst : instructions) { CompileIRInst(inst); @@ -87,9 +87,8 @@ bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u if (jo.Disabled(JitDisable::REGALLOC_GPR)) { gpr.FlushAll(); } - // TODO if (jo.Disabled(JitDisable::REGALLOC_FPR)) { - //fpr.FlushAll(); + fpr.FlushAll(); } // Safety check, in case we get a bunch of really large jit ops without a lot of branching. @@ -419,7 +418,7 @@ void RiscVJit::CompIR_Generic(IRInst inst) { void RiscVJit::FlushAll() { gpr.FlushAll(); - // TODO: fpr. + fpr.FlushAll(); } bool RiscVJit::DescribeCodePtr(const u8 *ptr, std::string &name) { diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index 5f32aee999..497bb175c5 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -24,6 +24,7 @@ #include "Core/MIPS/JitCommon/JitState.h" #include "Core/MIPS/JitCommon/JitCommon.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" +#include "Core/MIPS/RiscV/RiscVRegCacheFPU.h" namespace MIPSComp { @@ -113,6 +114,7 @@ private: RiscVGen::RiscVReg NormalizeR(IRRegIndex rs, IRRegIndex rd, RiscVGen::RiscVReg tempReg); RiscVRegCache gpr; + RiscVRegCacheFPU fpr; static constexpr int MAX_ALLOWED_JIT_BLOCKS = 262144; diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index 6c966dabe7..639c11843a 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -15,15 +15,15 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#ifndef offsetof +#include +#endif + #include "Common/CPUDetect.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" #include "Core/MIPS/JitCommon/JitState.h" #include "Core/Reporting.h" -#ifndef offsetof -#include "stddef.h" -#endif - using namespace RiscVGen; using namespace RiscVJitConstants; @@ -36,20 +36,14 @@ void RiscVRegCache::Init(RiscVEmitter *emitter) { } void RiscVRegCache::Start() { - for (int i = 0; i < NUM_RVREG; i++) { - ar[i].mipsReg = IRREG_INVALID; - ar[i].isDirty = false; - ar[i].pointerified = false; - ar[i].tempLocked = false; - ar[i].normalized32 = false; - } - for (int i = 0; i < NUM_MIPSREG; i++) { - mr[i].loc = MIPSLoc::MEM; - mr[i].reg = INVALID_REG; - mr[i].imm = -1; - mr[i].spillLock = false; - mr[i].isStatic = false; + if (!initialReady_) { + SetupInitialRegs(); + initialReady_ = true; } + + memcpy(ar, arInitial_, sizeof(ar)); + memcpy(mr, mrInitial_, sizeof(mr)); + int numStatics; const StaticAllocation *statics = GetStaticAllocations(numStatics); for (int i = 0; i < numStatics; i++) { @@ -61,24 +55,41 @@ void RiscVRegCache::Start() { mr[statics[i].mr].isStatic = true; mr[statics[i].mr].spillLock = true; } +} + +void RiscVRegCache::SetupInitialRegs() { + for (int i = 0; i < NUM_RVREG; i++) { + arInitial_[i].mipsReg = IRREG_INVALID; + arInitial_[i].isDirty = false; + arInitial_[i].pointerified = false; + arInitial_[i].tempLocked = false; + arInitial_[i].normalized32 = false; + } + for (int i = 0; i < NUM_MIPSREG; i++) { + mrInitial_[i].loc = MIPSLoc::MEM; + mrInitial_[i].reg = INVALID_REG; + mrInitial_[i].imm = -1; + mrInitial_[i].spillLock = false; + mrInitial_[i].isStatic = false; + } // Treat R_ZERO a bit specially, but it's basically static alloc too. - ar[R_ZERO].mipsReg = MIPS_REG_ZERO; - ar[R_ZERO].normalized32 = true; - mr[MIPS_REG_ZERO].loc = MIPSLoc::RVREG_IMM; - mr[MIPS_REG_ZERO].reg = R_ZERO; - mr[MIPS_REG_ZERO].imm = 0; - mr[MIPS_REG_ZERO].isStatic = true; + arInitial_[R_ZERO].mipsReg = MIPS_REG_ZERO; + arInitial_[R_ZERO].normalized32 = true; + mrInitial_[MIPS_REG_ZERO].loc = MIPSLoc::RVREG_IMM; + mrInitial_[MIPS_REG_ZERO].reg = R_ZERO; + mrInitial_[MIPS_REG_ZERO].imm = 0; + mrInitial_[MIPS_REG_ZERO].isStatic = true; } const RiscVReg *RiscVRegCache::GetMIPSAllocationOrder(int &count) { // X8 and X9 are the most ideal for static alloc because they can be used with compression. // Otherwise we stick to saved regs - might not be necessary. static const RiscVReg allocationOrder[] = { - X7, X8, X9, X12, X13, X14, X5, X6, X15, X16, X17, X18, X19, X20, X21, X22, X23, X28, X29, X30, X31, + X8, X9, X12, X13, X14, X15, X5, X6, X7, X16, X17, X18, X19, X20, X21, X22, X23, X28, X29, X30, X31, }; static const RiscVReg allocationOrderStaticAlloc[] = { - X7, X12, X13, X14, X5, X6, X15, X16, X17, X21, X22, X23, X28, X29, X30, X31, + X12, X13, X14, X15, X5, X6, X7, X16, X17, X21, X22, X23, X28, X29, X30, X31, }; if (jo_->useStaticAlloc) { @@ -432,6 +443,7 @@ RiscVReg RiscVRegCache::GetAndLockTempR() { RiscVReg reg = AllocateReg(); if (reg != INVALID_REG) { ar[reg].tempLocked = true; + pendingUnlock_ = true; } return reg; } @@ -1008,9 +1020,13 @@ void RiscVRegCache::SpillLock(IRRegIndex r1, IRRegIndex r2, IRRegIndex r3, IRReg if (r2 != IRREG_INVALID) mr[r2].spillLock = true; if (r3 != IRREG_INVALID) mr[r3].spillLock = true; if (r4 != IRREG_INVALID) mr[r4].spillLock = true; + pendingUnlock_ = true; } void RiscVRegCache::ReleaseSpillLocksAndDiscardTemps() { + if (!pendingUnlock_) + return; + for (int i = 0; i < NUM_MIPSREG; i++) { if (!mr[i].isStatic) mr[i].spillLock = false; @@ -1018,6 +1034,8 @@ void RiscVRegCache::ReleaseSpillLocksAndDiscardTemps() { for (int i = 0; i < NUM_RVREG; i++) { ar[i].tempLocked = false; } + + pendingUnlock_ = false; } void RiscVRegCache::ReleaseSpillLock(IRRegIndex r1, IRRegIndex r2, IRRegIndex r3, IRRegIndex r4) { diff --git a/Core/MIPS/RiscV/RiscVRegCache.h b/Core/MIPS/RiscV/RiscVRegCache.h index b3a8835152..72d680dc9b 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.h +++ b/Core/MIPS/RiscV/RiscVRegCache.h @@ -68,10 +68,6 @@ enum class MapType { } // namespace RiscVJitConstants -namespace MIPSAnalyst { -struct AnalysisResults; -}; - namespace MIPSComp { struct JitOptions; } @@ -175,6 +171,8 @@ private: bool IsValidReg(IRRegIndex r) const; bool IsValidRegNoZero(IRRegIndex r) const; + void SetupInitialRegs(); + MIPSState *mips_; RiscVGen::RiscVEmitter *emit_ = nullptr; MIPSComp::JitOptions *jo_; @@ -186,4 +184,9 @@ private: RegStatusRiscV ar[NUM_RVREG]{}; RegStatusMIPS mr[NUM_MIPSREG]{}; + + bool initialReady_ = false; + bool pendingUnlock_ = false; + RegStatusRiscV arInitial_[NUM_RVREG]; + RegStatusMIPS mrInitial_[NUM_MIPSREG]; }; diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp index 7287ea10b7..8a4e0f5f30 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp @@ -14,3 +14,375 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#ifndef offsetof +#include +#endif + +#include "Common/CPUDetect.h" +#include "Core/MIPS/RiscV/RiscVRegCacheFPU.h" +#include "Core/MIPS/JitCommon/JitState.h" +#include "Core/Reporting.h" + +using namespace RiscVGen; +using namespace RiscVJitConstants; + +using namespace RiscVGen; +using namespace RiscVJitConstants; + +RiscVRegCacheFPU::RiscVRegCacheFPU(MIPSState *mipsState, MIPSComp::JitOptions *jo) + : mips_(mipsState), jo_(jo) {} + +void RiscVRegCacheFPU::Init(RiscVEmitter *emitter) { + emit_ = emitter; +} + +void RiscVRegCacheFPU::Start() { + if (!initialReady_) { + SetupInitialRegs(); + initialReady_ = true; + } + + memcpy(ar, arInitial_, sizeof(ar)); + memcpy(mr, mrInitial_, sizeof(mr)); + pendingFlush_ = false; +} + +void RiscVRegCacheFPU::SetupInitialRegs() { + for (int i = 0; i < NUM_RVFPUREG; i++) { + ar[i].mipsReg = IRREG_INVALID; + ar[i].isDirty = false; + } + for (int i = 0; i < NUM_MIPSFPUREG; i++) { + mr[i].loc = MIPSLoc::MEM; + mr[i].reg = (int)INVALID_REG; + mr[i].spillLock = false; + } +} + +const RiscVReg *RiscVRegCacheFPU::GetMIPSAllocationOrder(int &count) { + // F8 through F15 are used for compression, so they are great. + // TODO: Maybe we could remove some saved regs since we rarely need that many? Or maybe worth it? + static const RiscVReg allocationOrder[] = { + F8, F9, F10, F11, F12, F13, F14, F15, + F0, F1, F2, F3, F4, F5, F6, F7, + F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31, + }; + + count = ARRAY_SIZE(allocationOrder); + return allocationOrder; +} + +bool RiscVRegCacheFPU::IsInRAM(IRRegIndex reg) { + _dbg_assert_(IsValidReg(reg)); + return mr[reg].loc == MIPSLoc::MEM; +} + +bool RiscVRegCacheFPU::IsMapped(IRRegIndex mipsReg) { + _dbg_assert_(IsValidReg(mipsReg)); + return mr[mipsReg].loc == MIPSLoc::RVREG; +} + +RiscVReg RiscVRegCacheFPU::MapReg(IRRegIndex mipsReg, MIPSMap mapFlags) { + _dbg_assert_(IsValidReg(mipsReg)); + _dbg_assert_(mr[mipsReg].loc == MIPSLoc::MEM || mr[mipsReg].loc == MIPSLoc::RVREG); + + pendingFlush_ = true; + + // Let's see if it's already mapped. If so we just need to update the dirty flag. + // We don't need to check for NOINIT because we assume that anyone who maps + // with that flag immediately writes a "known" value to the register. + if (mr[mipsReg].loc == MIPSLoc::RVREG) { + _assert_msg_(ar[mr[mipsReg].reg].mipsReg == mipsReg, "GPU mapping out of sync, IR=%i", mipsReg); + if ((mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY) { + ar[mr[mipsReg].reg].isDirty = true; + } + return (RiscVReg)(mr[mipsReg].reg + F0); + } + + // Okay, not mapped, so we need to allocate an RV register. + RiscVReg reg = AllocateReg(); + if (reg != INVALID_REG) { + // That means it's free. Grab it, and load the value into it (if requested). + ar[reg - F0].isDirty = (mapFlags & MIPSMap::DIRTY) == MIPSMap::DIRTY; + if ((mapFlags & MIPSMap::NOINIT) != MIPSMap::NOINIT) { + if (mr[mipsReg].loc == MIPSLoc::MEM) { + emit_->FL(32, reg, CTXREG, GetMipsRegOffset(mipsReg)); + } + } + ar[reg - F0].mipsReg = mipsReg; + mr[mipsReg].loc = MIPSLoc::RVREG; + mr[mipsReg].reg = reg - F0; + return reg; + } + + return reg; +} + +RiscVReg RiscVRegCacheFPU::AllocateReg() { + int allocCount = 0; + const RiscVReg *allocOrder = GetMIPSAllocationOrder(allocCount); + +allocate: + for (int i = 0; i < allocCount; i++) { + RiscVReg reg = allocOrder[i]; + + if (ar[reg - F0].mipsReg == IRREG_INVALID) { + return reg; + } + } + + // Still nothing. Let's spill a reg and goto 10. + // TODO: Use age or something to choose which register to spill? + // TODO: Spill dirty regs first? or opposite? + bool clobbered; + RiscVReg bestToSpill = FindBestToSpill(true, &clobbered); + if (bestToSpill == INVALID_REG) { + bestToSpill = FindBestToSpill(false, &clobbered); + } + + if (bestToSpill != INVALID_REG) { + if (clobbered) { + DiscardR(ar[bestToSpill - F0].mipsReg); + } else { + FlushRiscVReg(bestToSpill); + } + // Now one must be free. + goto allocate; + } + + // Uh oh, we have all of them spilllocked.... + ERROR_LOG_REPORT(JIT, "Out of spillable registers near PC %08x", mips_->pc); + _assert_(bestToSpill != INVALID_REG); + return INVALID_REG; +} + +RiscVReg RiscVRegCacheFPU::FindBestToSpill(bool unusedOnly, bool *clobbered) { + int allocCount = 0; + const RiscVReg *allocOrder = GetMIPSAllocationOrder(allocCount); + + static const int UNUSED_LOOKAHEAD_OPS = 30; + + *clobbered = false; + for (int i = 0; i < allocCount; i++) { + RiscVReg reg = allocOrder[i]; + if (ar[reg - F0].mipsReg != IRREG_INVALID && mr[ar[reg - F0].mipsReg].spillLock) + continue; + + // TODO: Look for clobbering in the IRInst array with index? + + // Not awesome. A used reg. Let's try to avoid spilling. + // TODO: Actually check if we'd be spilling. + if (unusedOnly) { + continue; + } + + return reg; + } + + return INVALID_REG; +} + +void RiscVRegCacheFPU::MapInIn(IRRegIndex rd, IRRegIndex rs) { + SpillLock(rd, rs); + MapReg(rd); + MapReg(rs); + ReleaseSpillLock(rd); + ReleaseSpillLock(rs); +} + +void RiscVRegCacheFPU::MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad) { + SpillLock(rd, rs); + bool load = !avoidLoad || rd == rs; + MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT); + MapReg(rs); + ReleaseSpillLock(rd); + ReleaseSpillLock(rs); +} + +void RiscVRegCacheFPU::MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad) { + SpillLock(rd, rs, rt); + bool load = !avoidLoad || (rd == rs || rd == rt); + MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT); + MapReg(rt); + MapReg(rs); + ReleaseSpillLock(rd); + ReleaseSpillLock(rs); + ReleaseSpillLock(rt); +} + +void RiscVRegCacheFPU::FlushRiscVReg(RiscVReg r) { + _dbg_assert_(r >= F0 && r <= F31); + int reg = r - F0; + if (ar[reg].mipsReg == IRREG_INVALID) { + // Nothing to do, reg not mapped. + return; + } + if (ar[reg].isDirty && mr[ar[reg].mipsReg].loc == MIPSLoc::RVREG) { + emit_->FS(32, r, CTXREG, GetMipsRegOffset(ar[reg].mipsReg)); + } + mr[ar[reg].mipsReg].loc = MIPSLoc::MEM; + mr[ar[reg].mipsReg].reg = (int)INVALID_REG; + ar[reg].mipsReg = IRREG_INVALID; + ar[reg].isDirty = false; +} + +void RiscVRegCacheFPU::FlushR(IRRegIndex r) { + _dbg_assert_(IsValidReg(r)); + RiscVReg reg = RiscVRegForFlush(r); + if (reg != INVALID_REG) + FlushRiscVReg(reg); +} + +RiscVReg RiscVRegCacheFPU::RiscVRegForFlush(IRRegIndex r) { + _dbg_assert_(IsValidReg(r)); + switch (mr[r].loc) { + case MIPSLoc::RVREG: + _assert_msg_(mr[r].reg != INVALID_REG, "RiscVRegForFlush: IR %d had bad RiscVReg", r); + if (mr[r].reg == INVALID_REG) { + return INVALID_REG; + } + return (RiscVReg)(F0 + mr[r].reg); + + case MIPSLoc::MEM: + return INVALID_REG; + + default: + _assert_(false); + return INVALID_REG; + } +} + +void RiscVRegCacheFPU::FlushAll() { + if (!pendingFlush_) { + // Nothing allocated. FPU regs are not nearly as common as GPR. + return; + } + + int numRVRegs = 0; + const RiscVReg *order = GetMIPSAllocationOrder(numRVRegs); + + for (int i = 0; i < numRVRegs; i++) { + int a = order[i] - F0; + int m = ar[a].mipsReg; + + if (ar[a].isDirty) { + _assert_(m != MIPS_REG_INVALID); + emit_->FS(32, order[i], CTXREG, GetMipsRegOffset(m)); + + mr[m].loc = MIPSLoc::MEM; + mr[m].reg = (int)INVALID_REG; + ar[a].mipsReg = IRREG_INVALID; + ar[a].isDirty = false; + } else { + if (m != IRREG_INVALID) { + mr[m].loc = MIPSLoc::MEM; + mr[m].reg = (int)INVALID_REG; + } + ar[a].mipsReg = IRREG_INVALID; + } + } + + pendingFlush_ = false; +} + +void RiscVRegCacheFPU::DiscardR(IRRegIndex r) { + _dbg_assert_(IsValidReg(r)); + switch (mr[r].loc) { + case MIPSLoc::RVREG: + _assert_(mr[r].reg != INVALID_REG); + if (mr[r].reg != INVALID_REG) { + // Note that we DO NOT write it back here. That's the whole point of Discard. + ar[mr[r].reg].isDirty = false; + ar[mr[r].reg].mipsReg = IRREG_INVALID; + } + break; + + case MIPSLoc::MEM: + // Already there, nothing to do. + break; + + default: + _assert_(false); + break; + } + mr[r].loc = MIPSLoc::MEM; + mr[r].reg = (int)INVALID_REG; + mr[r].spillLock = false; +} + +int RiscVRegCacheFPU::GetMipsRegOffset(IRRegIndex r) { + _assert_(IsValidReg(r)); + // These are offsets within the MIPSState structure. + // IR gives us an index that is already 32 after the state index (skipping GPRs.) + return (32 + r) * 4; +} + +void RiscVRegCacheFPU::SpillLock(IRRegIndex r1, IRRegIndex r2, IRRegIndex r3, IRRegIndex r4) { + _dbg_assert_(IsValidReg(r1)); + _dbg_assert_(r2 == IRREG_INVALID || IsValidReg(r2)); + _dbg_assert_(r3 == IRREG_INVALID || IsValidReg(r3)); + _dbg_assert_(r4 == IRREG_INVALID || IsValidReg(r4)); + mr[r1].spillLock = true; + if (r2 != IRREG_INVALID) + mr[r2].spillLock = true; + if (r3 != IRREG_INVALID) + mr[r3].spillLock = true; + if (r4 != IRREG_INVALID) + mr[r4].spillLock = true; + pendingUnlock_ = true; +} + +void RiscVRegCacheFPU::ReleaseSpillLocksAndDiscardTemps() { + if (!pendingUnlock_) + return; + + for (int i = 0; i < NUM_MIPSFPUREG; i++) { + mr[i].spillLock = false; + } + + pendingUnlock_ = false; +} + +void RiscVRegCacheFPU::ReleaseSpillLock(IRRegIndex r1, IRRegIndex r2, IRRegIndex r3, IRRegIndex r4) { + _dbg_assert_(IsValidReg(r1)); + _dbg_assert_(r2 == IRREG_INVALID || IsValidReg(r2)); + _dbg_assert_(r3 == IRREG_INVALID || IsValidReg(r3)); + _dbg_assert_(r4 == IRREG_INVALID || IsValidReg(r4)); + mr[r1].spillLock = false; + if (r2 != IRREG_INVALID) + mr[r2].spillLock = false; + if (r3 != IRREG_INVALID) + mr[r3].spillLock = false; + if (r4 != IRREG_INVALID) + mr[r4].spillLock = false; +} + +RiscVReg RiscVRegCacheFPU::R(IRRegIndex mipsReg) { + _dbg_assert_(IsValidReg(mipsReg)); + _dbg_assert_(mr[mipsReg].loc == MIPSLoc::RVREG); + if (mr[mipsReg].loc == MIPSLoc::RVREG) { + return (RiscVReg)(mr[mipsReg].reg + F0); + } else { + ERROR_LOG_REPORT(JIT, "Reg %i not in riscv reg", mipsReg); + return INVALID_REG; // BAAAD + } +} + +bool RiscVRegCacheFPU::IsValidReg(IRRegIndex r) const { + if (r < 0 || r >= NUM_MIPSFPUREG) + return false; + + // See MIPSState for these offsets. + int index = r + 32; + + // Allow FPU or VFPU regs here. + if (index >= 32 && index < 32 + 128) + return true; + // Also allow VFPU temps. + if (index >= 224 && index < 224 + 16) + return true; + + // Nothing else is allowed for the FPU side cache. + return false; +} diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.h b/Core/MIPS/RiscV/RiscVRegCacheFPU.h index 48b5ab53e0..c861c6599b 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.h +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.h @@ -16,3 +16,88 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #pragma once + +#include "Common/RiscVEmitter.h" +#include "Core/MIPS/MIPS.h" +#include "Core/MIPS/RiscV/RiscVRegCache.h" + +struct FPURegStatusRiscV { + int mipsReg; // if -1, no mipsreg attached. + bool isDirty; // Should the register be written back? +}; + +struct FPURegStatusMIPS { + // Where is this MIPS register? + RiscVJitConstants::MIPSLoc loc; + // Index from F0. + int reg; + + bool spillLock; // if true, this register cannot be spilled. + // If loc == ML_MEM, it's back in its location in the CPU context struct. +}; + +namespace MIPSComp { +struct JitOptions; +} + +class RiscVRegCacheFPU { +public: + RiscVRegCacheFPU(MIPSState *mipsState, MIPSComp::JitOptions *jo); + ~RiscVRegCacheFPU() {} + + void Init(RiscVGen::RiscVEmitter *emitter); + // TODO: Maybe pass in IR block and start PC for logging/debugging? + void Start(); + + // Protect the RISC-V register containing a MIPS register from spilling, to ensure that + // it's being kept allocated. + void SpillLock(IRRegIndex reg, IRRegIndex reg2 = IRREG_INVALID, IRRegIndex reg3 = IRREG_INVALID, IRRegIndex reg4 = IRREG_INVALID); + void ReleaseSpillLock(IRRegIndex reg, IRRegIndex reg2 = IRREG_INVALID, IRRegIndex reg3 = IRREG_INVALID, IRRegIndex reg4 = IRREG_INVALID); + void ReleaseSpillLocksAndDiscardTemps(); + + // Returns a RISC-V register containing the requested MIPS register. + RiscVGen::RiscVReg MapReg(IRRegIndex reg, RiscVJitConstants::MIPSMap mapFlags = RiscVJitConstants::MIPSMap::INIT); + + bool IsMapped(IRRegIndex r); + bool IsInRAM(IRRegIndex r); + + void MapInIn(IRRegIndex rd, IRRegIndex rs); + void MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad = true); + void MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad = true); + void FlushAll(); + void FlushR(IRRegIndex r); + void DiscardR(IRRegIndex r); + + RiscVGen::RiscVReg R(int preg); // Returns a cached register + +private: + const RiscVGen::RiscVReg *GetMIPSAllocationOrder(int &count); + RiscVGen::RiscVReg AllocateReg(); + RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered); + RiscVGen::RiscVReg RiscVRegForFlush(IRRegIndex r); + void FlushRiscVReg(RiscVGen::RiscVReg r); + int GetMipsRegOffset(IRRegIndex r); + + bool IsValidReg(IRRegIndex r) const; + + void SetupInitialRegs(); + + MIPSState *mips_; + RiscVGen::RiscVEmitter *emit_ = nullptr; + MIPSComp::JitOptions *jo_; + + enum { + // On RiscV, each of the 32 registers are full 128-bit. No sharing of components! + NUM_RVFPUREG = 32, + NUM_MIPSFPUREG = RiscVJitConstants::TOTAL_MAPPABLE_MIPSREGS - 32, + }; + + FPURegStatusRiscV ar[NUM_RVFPUREG]; + FPURegStatusMIPS mr[NUM_MIPSFPUREG]; + + bool pendingFlush_ = false; + bool pendingUnlock_ = false; + bool initialReady_ = false; + FPURegStatusRiscV arInitial_[NUM_RVFPUREG]; + FPURegStatusMIPS mrInitial_[NUM_MIPSFPUREG]; +}; From 7071884a4748234e7938b6d387997623e856d50f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 24 Jul 2023 20:49:40 -0700 Subject: [PATCH 102/167] riscv: Handle rounding mode and ctrl transfers. --- Core/MIPS/RiscV/RiscVAsm.cpp | 38 ++-------------------------- Core/MIPS/RiscV/RiscVCompBranch.cpp | 3 ++- Core/MIPS/RiscV/RiscVCompFPU.cpp | 9 +++++-- Core/MIPS/RiscV/RiscVCompSystem.cpp | 25 ++++++++++++++++++ Core/MIPS/RiscV/RiscVJit.cpp | 6 +++-- Core/MIPS/RiscV/RiscVJit.h | 1 + Core/MIPS/RiscV/RiscVRegCacheFPU.cpp | 12 ++++----- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVAsm.cpp b/Core/MIPS/RiscV/RiscVAsm.cpp index 7fe1ce8b53..67dead2e1e 100644 --- a/Core/MIPS/RiscV/RiscVAsm.cpp +++ b/Core/MIPS/RiscV/RiscVAsm.cpp @@ -75,9 +75,8 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { loadStaticRegisters_ = nullptr; } - // TODO: Do we actually need updateRoundingMode_? Hm. - //applyRoundingMode_ = AlignCode16(); - if (false) { + applyRoundingMode_ = AlignCode16(); + { // Not sure if RISC-V has any flush to zero capability? Leaving it off for now... LWU(SCRATCH2, CTXREG, offsetof(MIPSState, fcr31)); @@ -105,30 +104,6 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { RET(); } - //updateRoundingMode_ = AlignCode16(); - if (false) { - LWU(SCRATCH2, CTXREG, offsetof(MIPSState, fcr31)); - - // Set SCRATCH2 to FZ:RM (FZ is bit 24, and RM are lowest 2 bits.) - ANDI(SCRATCH1, SCRATCH2, 1 << 24); - ANDI(SCRATCH2, SCRATCH2, 3); - SRLI(SCRATCH1, SCRATCH1, 22); - OR(SCRATCH2, SCRATCH2, SCRATCH1); - - // Let's update js.currentRoundingFunc with the right convertS0ToSCRATCH1 func. - //LI(SCRATCH1, convertS0ToSCRATCH1); - if (cpu_info.RiscV_Zba) { - SH_ADD(3, SCRATCH1, SCRATCH2, SCRATCH1); - } else { - SLLI(SCRATCH2, SCRATCH2, 3); - ADD(SCRATCH1, SCRATCH1, SCRATCH2); - } - LD(SCRATCH2, SCRATCH1, 0); - //LI(SCRATCH1, &js.currentRoundingFunc); - SW(SCRATCH2, SCRATCH1, 0); - RET(); - } - enterDispatcher_ = AlignCode16(); // Start by saving some regs on the stack. There are 12 GPs and 12 FPs we want. @@ -280,15 +255,6 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { SW(SCRATCH2, SCRATCH1, 0); J(quitLoop); - // TODO: Do we need this? - static const Round roundModes[8] = { Round::NEAREST_EVEN, Round::TOZERO, Round::UP, Round::DOWN, Round::NEAREST_EVEN, Round::TOZERO, Round::UP, Round::DOWN }; - for (size_t i = 0; i < ARRAY_SIZE(roundModes); ++i) { - //convertS0ToSCRATCH1[i] = AlignCode16(); - - //FCVT(FConv::W, FConv::S, SCRATCH1, F0, roundModes[i]); - //RET(); - } - // Leave this at the end, add more stuff above. if (enableDisasm) { std::vector lines = DisassembleRV64(start, GetCodePtr() - start); diff --git a/Core/MIPS/RiscV/RiscVCompBranch.cpp b/Core/MIPS/RiscV/RiscVCompBranch.cpp index c4b9c80c9a..aa9d2467a0 100644 --- a/Core/MIPS/RiscV/RiscVCompBranch.cpp +++ b/Core/MIPS/RiscV/RiscVCompBranch.cpp @@ -134,7 +134,8 @@ void RiscVJit::CompIR_ExitIf(IRInst inst) { case IROp::ExitToConstIfFpTrue: case IROp::ExitToConstIfFpFalse: - CompIR_Generic(inst); + // Note: not used. + DISABLE; break; default: diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index ef8e6a8db8..25d4799a30 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -135,7 +135,6 @@ void RiscVJit::CompIR_FCompare(IRInst inst) { CONDITIONAL_DISABLE; switch (inst.op) { - case IROp::ZeroFpCond: case IROp::FCmp: case IROp::FCmovVfpuCC: case IROp::FCmpVfpuBit: @@ -154,9 +153,15 @@ void RiscVJit::CompIR_RoundingMode(IRInst inst) { switch (inst.op) { case IROp::RestoreRoundingMode: + RestoreRoundingMode(); + break; + case IROp::ApplyRoundingMode: + ApplyRoundingMode(); + break; + case IROp::UpdateRoundingMode: - CompIR_Generic(inst); + // We don't need to do anything, instructions allow a "dynamic" rounding mode. break; default: diff --git a/Core/MIPS/RiscV/RiscVCompSystem.cpp b/Core/MIPS/RiscV/RiscVCompSystem.cpp index fa613c6ce5..10486adf2d 100644 --- a/Core/MIPS/RiscV/RiscVCompSystem.cpp +++ b/Core/MIPS/RiscV/RiscVCompSystem.cpp @@ -83,10 +83,35 @@ void RiscVJit::CompIR_Transfer(IRInst inst) { switch (inst.op) { case IROp::SetCtrlVFPU: + gpr.SetImm(IRREG_VFPU_CTRL_BASE + inst.dest, (int32_t)inst.constant); + break; + case IROp::SetCtrlVFPUReg: + gpr.MapDirtyIn(IRREG_VFPU_CTRL_BASE + inst.dest, inst.src1); + MV(gpr.R(IRREG_VFPU_CTRL_BASE + inst.dest), gpr.R(inst.src1)); + gpr.MarkDirty(gpr.R(IRREG_VFPU_CTRL_BASE + inst.dest), gpr.IsNormalized32(inst.src1)); + break; + case IROp::SetCtrlVFPUFReg: + CompIR_Generic(inst); + break; + case IROp::FpCondToReg: + gpr.MapDirtyIn(inst.dest, IRREG_FPCOND); + MV(gpr.R(inst.dest), gpr.R(IRREG_FPCOND)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(IRREG_FPCOND)); + break; + + case IROp::ZeroFpCond: + gpr.SetImm(IRREG_FPCOND, 0); + break; + case IROp::VfpuCtrlToReg: + gpr.MapDirtyIn(inst.dest, IRREG_VFPU_CTRL_BASE + inst.src1); + MV(gpr.R(inst.dest), gpr.R(IRREG_VFPU_CTRL_BASE + inst.src1)); + gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(IRREG_VFPU_CTRL_BASE + inst.src1)); + break; + case IROp::FMovFromGPR: case IROp::FMovToGPR: CompIR_Generic(inst); diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 11e98d9f59..b38aeb5124 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -273,7 +273,6 @@ void RiscVJit::CompileIRInst(IRInst inst) { CompIR_FSat(inst); break; - case IROp::ZeroFpCond: case IROp::FCmp: case IROp::FCmovVfpuCC: case IROp::FCmpVfpuBit: @@ -291,6 +290,7 @@ void RiscVJit::CompileIRInst(IRInst inst) { case IROp::SetCtrlVFPUReg: case IROp::SetCtrlVFPUFReg: case IROp::FpCondToReg: + case IROp::ZeroFpCond: case IROp::VfpuCtrlToReg: case IROp::FMovFromGPR: case IROp::FMovToGPR: @@ -435,6 +435,8 @@ bool RiscVJit::DescribeCodePtr(const u8 *ptr, std::string &name) { name = "loadStaticRegisters"; } else if (ptr == enterDispatcher_) { name = "enterDispatcher"; + } else if (ptr == applyRoundingMode_) { + name = "applyRoundingMode"; } else if (!IsInSpace(ptr)) { return false; } else { @@ -507,7 +509,7 @@ void RiscVJit::RestoreRoundingMode(bool force) { void RiscVJit::ApplyRoundingMode(bool force) { // TODO: Also could maybe sometimes skip? - //QuickCallFunction(applyRoundingMode_); + QuickCallFunction(applyRoundingMode_); } void RiscVJit::MovFromPC(RiscVReg r) { diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index 497bb175c5..79114d6af6 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -127,6 +127,7 @@ private: const u8 *dispatcher_ = nullptr; const u8 *dispatcherNoCheck_ = nullptr; const u8 *dispatcherFetch_ = nullptr; + const u8 *applyRoundingMode_ = nullptr; const u8 *saveStaticRegisters_ = nullptr; const u8 *loadStaticRegisters_ = nullptr; diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp index 8a4e0f5f30..f4ddba03ad 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp @@ -50,13 +50,13 @@ void RiscVRegCacheFPU::Start() { void RiscVRegCacheFPU::SetupInitialRegs() { for (int i = 0; i < NUM_RVFPUREG; i++) { - ar[i].mipsReg = IRREG_INVALID; - ar[i].isDirty = false; + arInitial_[i].mipsReg = IRREG_INVALID; + arInitial_[i].isDirty = false; } for (int i = 0; i < NUM_MIPSFPUREG; i++) { - mr[i].loc = MIPSLoc::MEM; - mr[i].reg = (int)INVALID_REG; - mr[i].spillLock = false; + mrInitial_[i].loc = MIPSLoc::MEM; + mrInitial_[i].reg = (int)INVALID_REG; + mrInitial_[i].spillLock = false; } } @@ -377,7 +377,7 @@ bool RiscVRegCacheFPU::IsValidReg(IRRegIndex r) const { int index = r + 32; // Allow FPU or VFPU regs here. - if (index >= 32 && index < 32 + 128) + if (index >= 32 && index < 32 + 32 + 128) return true; // Also allow VFPU temps. if (index >= 224 && index < 224 + 16) From bb6fdd0246775c9093728c1d31fc8eade47af4ca Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 24 Jul 2023 22:56:55 -0700 Subject: [PATCH 103/167] riscv: Add floating point load/stores. --- Core/MIPS/RiscV/RiscVCompLoadStore.cpp | 98 ++++++++++++++++++++++++-- Core/MIPS/RiscV/RiscVJit.h | 2 +- 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp index fd0ca6392d..a1e0ae08c4 100644 --- a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp +++ b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp @@ -53,8 +53,8 @@ void RiscVJit::SetScratch1ToSrc1Address(IRReg src1) { #endif } -int32_t RiscVJit::AdjustForAddressOffset(RiscVGen::RiscVReg *reg, int32_t constant) { - if (constant < -2048 || constant > 2047) { +int32_t RiscVJit::AdjustForAddressOffset(RiscVGen::RiscVReg *reg, int32_t constant, int32_t range) { + if (constant < -2048 || constant + range > 2047) { LI(SCRATCH2, constant); ADD(SCRATCH1, *reg, SCRATCH2); *reg = SCRATCH1; @@ -124,6 +124,7 @@ void RiscVJit::CompIR_LoadShift(IRInst inst) { switch (inst.op) { case IROp::Load32Left: case IROp::Load32Right: + // Should not happen if the pass to split is active. CompIR_Generic(inst); break; @@ -136,9 +137,28 @@ void RiscVJit::CompIR_LoadShift(IRInst inst) { void RiscVJit::CompIR_FLoad(IRInst inst) { CONDITIONAL_DISABLE; + RiscVReg addrReg = INVALID_REG; + if (inst.src1 == MIPS_REG_ZERO) { + // This will get changed by AdjustForAddressOffset. + addrReg = MEMBASEREG; +#ifdef MASKED_PSP_MEMORY + inst.constant &= Memory::MEMVIEW32_MASK; +#endif + } else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) { + addrReg = gpr.MapRegAsPointer(inst.src1); + } else { + SetScratch1ToSrc1Address(inst.src1); + addrReg = SCRATCH1; + } + + s32 imm = AdjustForAddressOffset(&addrReg, inst.constant); + + // TODO: Safe memory? Or enough to have crash handler + validate? + switch (inst.op) { case IROp::LoadFloat: - CompIR_Generic(inst); + fpr.MapReg(inst.dest, MIPSMap::NOINIT); + FL(32, fpr.R(inst.dest), addrReg, imm); break; default: @@ -150,9 +170,32 @@ void RiscVJit::CompIR_FLoad(IRInst inst) { void RiscVJit::CompIR_VecLoad(IRInst inst) { CONDITIONAL_DISABLE; + RiscVReg addrReg = INVALID_REG; + if (inst.src1 == MIPS_REG_ZERO) { + // This will get changed by AdjustForAddressOffset. + addrReg = MEMBASEREG; +#ifdef MASKED_PSP_MEMORY + inst.constant &= Memory::MEMVIEW32_MASK; +#endif + } else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) { + addrReg = gpr.MapRegAsPointer(inst.src1); + } else { + SetScratch1ToSrc1Address(inst.src1); + addrReg = SCRATCH1; + } + + // We need to be able to address the whole 16 bytes, so offset of 12. + s32 imm = AdjustForAddressOffset(&addrReg, inst.constant, 12); + + // TODO: Safe memory? Or enough to have crash handler + validate? + switch (inst.op) { case IROp::LoadVec4: - CompIR_Generic(inst); + for (int i = 0; i < 4; ++i) { + // Spilling is okay. + fpr.MapReg(inst.dest + i, MIPSMap::NOINIT); + FL(32, fpr.R(inst.dest + i), addrReg, imm + 4 * i); + } break; default: @@ -212,6 +255,7 @@ void RiscVJit::CompIR_StoreShift(IRInst inst) { switch (inst.op) { case IROp::Store32Left: case IROp::Store32Right: + // Should not happen if the pass to split is active. CompIR_Generic(inst); break; @@ -224,9 +268,28 @@ void RiscVJit::CompIR_StoreShift(IRInst inst) { void RiscVJit::CompIR_FStore(IRInst inst) { CONDITIONAL_DISABLE; + RiscVReg addrReg = INVALID_REG; + if (inst.src1 == MIPS_REG_ZERO) { + // This will get changed by AdjustForAddressOffset. + addrReg = MEMBASEREG; +#ifdef MASKED_PSP_MEMORY + inst.constant &= Memory::MEMVIEW32_MASK; +#endif + } else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) { + addrReg = gpr.MapRegAsPointer(inst.src1); + } else { + SetScratch1ToSrc1Address(inst.src1); + addrReg = SCRATCH1; + } + + s32 imm = AdjustForAddressOffset(&addrReg, inst.constant); + + // TODO: Safe memory? Or enough to have crash handler + validate? + switch (inst.op) { case IROp::StoreFloat: - CompIR_Generic(inst); + fpr.MapReg(inst.src3); + FS(32, fpr.R(inst.src3), addrReg, imm); break; default: @@ -238,9 +301,32 @@ void RiscVJit::CompIR_FStore(IRInst inst) { void RiscVJit::CompIR_VecStore(IRInst inst) { CONDITIONAL_DISABLE; + RiscVReg addrReg = INVALID_REG; + if (inst.src1 == MIPS_REG_ZERO) { + // This will get changed by AdjustForAddressOffset. + addrReg = MEMBASEREG; +#ifdef MASKED_PSP_MEMORY + inst.constant &= Memory::MEMVIEW32_MASK; +#endif + } else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) { + addrReg = gpr.MapRegAsPointer(inst.src1); + } else { + SetScratch1ToSrc1Address(inst.src1); + addrReg = SCRATCH1; + } + + // We need to be able to address the whole 16 bytes, so offset of 12. + s32 imm = AdjustForAddressOffset(&addrReg, inst.constant, 12); + + // TODO: Safe memory? Or enough to have crash handler + validate? + switch (inst.op) { case IROp::StoreVec4: - CompIR_Generic(inst); + for (int i = 0; i < 4; ++i) { + // Spilling is okay, though not ideal. + fpr.MapReg(inst.src3 + i); + FS(32, fpr.R(inst.src3 + i), addrReg, imm + 4 * i); + } break; default: diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index 79114d6af6..105090d9d7 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -108,7 +108,7 @@ private: void SetScratch1ToSrc1Address(IRReg src1); // Modifies SCRATCH regs. - int32_t AdjustForAddressOffset(RiscVGen::RiscVReg *reg, int32_t constant); + int32_t AdjustForAddressOffset(RiscVGen::RiscVReg *reg, int32_t constant, int32_t range = 0); void NormalizeSrc1(IRInst inst, RiscVGen::RiscVReg *reg, RiscVGen::RiscVReg tempReg, bool allowOverlap); void NormalizeSrc12(IRInst inst, RiscVGen::RiscVReg *lhs, RiscVGen::RiscVReg *rhs, RiscVGen::RiscVReg lhsTempReg, RiscVGen::RiscVReg rhsTempReg, bool allowOverlap); RiscVGen::RiscVReg NormalizeR(IRRegIndex rs, IRRegIndex rd, RiscVGen::RiscVReg tempReg); From 05360d5c7a186cbf5d7d86cb2b98348d00012c5d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 24 Jul 2023 22:58:23 -0700 Subject: [PATCH 104/167] riscv: Implement simplest float ops. --- Core/MIPS/IR/IRInterpreter.cpp | 2 ++ Core/MIPS/RiscV/RiscVCompFPU.cpp | 36 ++++++++++++++++++++++++++++- Core/MIPS/RiscV/RiscVCompSystem.cpp | 26 ++++++++++++++++++--- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 5b858bc1b3..7f576dd4ea 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -768,9 +768,11 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { mips->f[inst->dest] = mips->f[inst->src1] / mips->f[inst->src2]; break; case IROp::FMin: + // TODO: This doesn't handle VFPU ordering right. mips->f[inst->dest] = std::min(mips->f[inst->src1], mips->f[inst->src2]); break; case IROp::FMax: + // TODO: This doesn't handle VFPU ordering right. mips->f[inst->dest] = std::max(mips->f[inst->src1], mips->f[inst->src2]); break; diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index 25d4799a30..f8f1f1cae7 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -39,12 +39,37 @@ void RiscVJit::CompIR_FArith(IRInst inst) { switch (inst.op) { case IROp::FAdd: + fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + FADD(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2)); + break; + case IROp::FSub: + fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + FSUB(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2)); + break; + case IROp::FMul: + fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + if (inst.src1 != inst.src2) { + CompIR_Generic(inst); + } else { + FMUL(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2)); + } + break; + case IROp::FDiv: + fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + FDIV(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2)); + break; + case IROp::FSqrt: + fpr.MapDirtyIn(inst.dest, inst.src1); + FSQRT(32, fpr.R(inst.dest), fpr.R(inst.src1)); + break; + case IROp::FNeg: - CompIR_Generic(inst); + fpr.MapDirtyIn(inst.dest, inst.src1); + FNEG(32, fpr.R(inst.dest), fpr.R(inst.src1)); break; default: @@ -59,6 +84,7 @@ void RiscVJit::CompIR_FCondAssign(IRInst inst) { switch (inst.op) { case IROp::FMin: case IROp::FMax: + // TODO: These are tricky, have to handle order correctly. CompIR_Generic(inst); break; @@ -73,7 +99,15 @@ void RiscVJit::CompIR_FAssign(IRInst inst) { switch (inst.op) { case IROp::FMov: + fpr.MapDirtyIn(inst.dest, inst.src1); + FMV(32, fpr.R(inst.dest), fpr.R(inst.src1)); + break; + case IROp::FAbs: + fpr.MapDirtyIn(inst.dest, inst.src1); + FABS(32, fpr.R(inst.dest), fpr.R(inst.src1)); + break; + case IROp::FSign: CompIR_Generic(inst); break; diff --git a/Core/MIPS/RiscV/RiscVCompSystem.cpp b/Core/MIPS/RiscV/RiscVCompSystem.cpp index 10486adf2d..8a8b995f5f 100644 --- a/Core/MIPS/RiscV/RiscVCompSystem.cpp +++ b/Core/MIPS/RiscV/RiscVCompSystem.cpp @@ -50,7 +50,14 @@ void RiscVJit::CompIR_Basic(IRInst inst) { break; case IROp::SetConstF: - CompIR_Generic(inst); + fpr.MapReg(inst.dest, MIPSMap::NOINIT); + if (inst.constant == 0) { + FCVT(FConv::S, FConv::W, fpr.R(inst.dest), R_ZERO); + } else { + // TODO: In the future, could use FLI if it's approved. + LI(SCRATCH1, (int32_t)inst.constant); + FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1); + } break; case IROp::Downcount: @@ -93,7 +100,9 @@ void RiscVJit::CompIR_Transfer(IRInst inst) { break; case IROp::SetCtrlVFPUFReg: - CompIR_Generic(inst); + gpr.MapReg(IRREG_VFPU_CTRL_BASE + inst.dest, MIPSMap::NOINIT); + fpr.MapReg(inst.src1); + FMV(FMv::X, FMv::W, gpr.R(IRREG_VFPU_CTRL_BASE + inst.dest), fpr.R(inst.src1)); break; case IROp::FpCondToReg: @@ -113,8 +122,19 @@ void RiscVJit::CompIR_Transfer(IRInst inst) { break; case IROp::FMovFromGPR: + fpr.MapReg(inst.dest, MIPSMap::NOINIT); + if (gpr.IsImm(inst.src1) && gpr.GetImm(inst.src1) == 0) { + FCVT(FConv::S, FConv::W, fpr.R(inst.dest), R_ZERO); + } else { + gpr.MapReg(inst.src1); + FMV(FMv::W, FMv::X, fpr.R(inst.dest), gpr.R(inst.src1)); + } + break; + case IROp::FMovToGPR: - CompIR_Generic(inst); + gpr.MapReg(inst.dest, MIPSMap::NOINIT); + fpr.MapReg(inst.src1); + FMV(FMv::X, FMv::W, gpr.R(inst.dest), fpr.R(inst.src1)); break; default: From 9a9b371856162867702670e1e20b58e52379d418 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 24 Jul 2023 22:58:40 -0700 Subject: [PATCH 105/167] riscv: Implement FSign using FCLASS. --- Core/MIPS/RiscV/RiscVCompFPU.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index f8f1f1cae7..a99cff0b86 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -109,8 +109,28 @@ void RiscVJit::CompIR_FAssign(IRInst inst) { break; case IROp::FSign: - CompIR_Generic(inst); + { + fpr.MapDirtyIn(inst.dest, inst.src1); + // Check if it's negative zero, either 0x10/0x08 is zero. + FCLASS(32, SCRATCH1, fpr.R(inst.src1)); + ANDI(SCRATCH1, SCRATCH1, 0x18); + SEQZ(SCRATCH1, SCRATCH1); + // Okay, it's zero if zero, 1 otherwise. Convert 1 to a constant 1.0. + // Probably non-zero is the common case, so we make that the straight line. + FixupBranch skipOne = BEQ(SCRATCH1, R_ZERO); + LI(SCRATCH1, 1.0f); + + // Now we just need the sign from it. + FMV(FMv::X, FMv::W, SCRATCH2, fpr.R(inst.src1)); + // Use a wall to isolate the sign, and combine. + SRAIW(SCRATCH2, SCRATCH2, 31); + SLLIW(SCRATCH2, SCRATCH2, 31); + OR(SCRATCH1, SCRATCH1, SCRATCH2); + + SetJumpTarget(skipOne); + FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1); break; + } default: INVALIDOP; From ca7a520a199eb805df1d05bfb139ed8c2de5b3dd Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 24 Jul 2023 22:59:08 -0700 Subject: [PATCH 106/167] riscv: Implement FMul. --- Core/MIPS/RiscV/RiscVCompFPU.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index a99cff0b86..27ab1a943f 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -51,7 +51,35 @@ void RiscVJit::CompIR_FArith(IRInst inst) { case IROp::FMul: fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); if (inst.src1 != inst.src2) { - CompIR_Generic(inst); + // These will output 0x80/0x01 if infinity, 0x10/0x80 if zero. + // We need to check if one is infinity and the other zero. + + // First, try inf * zero. + FCLASS(32, SCRATCH1, fpr.R(inst.src1)); + FCLASS(32, SCRATCH2, fpr.R(inst.src2)); + ANDI(R_RA, SCRATCH1, 0x81); + FixupBranch lhsNotInf = BEQ(R_RA, R_ZERO); + ANDI(R_RA, SCRATCH2, 0x18); + FixupBranch infZero = BNE(R_RA, R_ZERO); + + // Okay, what about the other order? + SetJumpTarget(lhsNotInf); + ANDI(R_RA, SCRATCH1, 0x18); + FixupBranch lhsNotZero = BEQ(R_RA, R_ZERO); + ANDI(R_RA, SCRATCH2, 0x81); + FixupBranch zeroInf = BNE(R_RA, R_ZERO); + + // Nope, all good. + SetJumpTarget(lhsNotZero); + FMUL(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2)); + FixupBranch skip = J(); + + SetJumpTarget(infZero); + SetJumpTarget(zeroInf); + LI(SCRATCH1, 0x7FC00000); + FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1); + + SetJumpTarget(skip); } else { FMUL(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2)); } From 9157d992acab9bdea780e0dce39e81b48745fa26 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 15 May 2016 19:58:40 -0700 Subject: [PATCH 107/167] jit-ir: Implement cfc1/ctc1. This makes it so we can track rounding mode changes. --- Core/MIPS/IR/IRCompFPU.cpp | 8 ++++++-- Core/MIPS/IR/IRInst.cpp | 5 +++++ Core/MIPS/IR/IRInst.h | 2 ++ Core/MIPS/IR/IRInterpreter.cpp | 11 +++++++++++ Core/MIPS/IR/IRPassSimplify.cpp | 6 ++++++ 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/IR/IRCompFPU.cpp b/Core/MIPS/IR/IRCompFPU.cpp index cf6b4faa75..10a8d156bc 100644 --- a/Core/MIPS/IR/IRCompFPU.cpp +++ b/Core/MIPS/IR/IRCompFPU.cpp @@ -203,7 +203,8 @@ void IRFrontend::Comp_mxc1(MIPSOpcode op) { return; } if (fs == 31) { - DISABLE; // TODO: Add a new op + // This needs to insert fpcond. + ir.Write(IROp::FpCtrlToReg, rt); } else if (fs == 0) { ir.Write(IROp::SetConst, rt, ir.AddConstant(MIPSState::FCR0_VALUE)); } else { @@ -219,7 +220,10 @@ void IRFrontend::Comp_mxc1(MIPSOpcode op) { case 6: //ctc1 if (fs == 31) { // Set rounding mode - DISABLE; + RestoreRoundingMode(); + ir.Write(IROp::FpCtrlFromReg, 0, rt); + UpdateRoundingMode(); + ApplyRoundingMode(); } else { Comp_Generic(op); } diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 6d3e6f9572..5723142a24 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -112,7 +112,12 @@ static const IRMeta irMeta[] = { { IROp::FMovToGPR, "FMovToGPR", "GF" }, { IROp::ZeroFpCond, "ZeroFpCond", "" }, { IROp::FpCondToReg, "FpCondToReg", "G" }, + { IROp::FpCtrlFromReg, "FpCtrlFromReg", "_G" }, + { IROp::FpCtrlToReg, "FpCtrlToReg", "G" }, { IROp::VfpuCtrlToReg, "VfpuCtrlToReg", "GI" }, + { IROp::RestoreRoundingMode, "RestoreRoundingMode", "" }, + { IROp::ApplyRoundingMode, "ApplyRoundingMode", "" }, + { IROp::UpdateRoundingMode, "UpdateRoundingMode", "" }, { IROp::SetCtrlVFPU, "SetCtrlVFPU", "TC" }, { IROp::SetCtrlVFPUReg, "SetCtrlVFPUReg", "TG" }, { IROp::SetCtrlVFPUFReg, "SetCtrlVFPUFReg", "TF" }, diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 85549c8d6a..d4eb47f49a 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -135,6 +135,8 @@ enum class IROp : u8 { FSatMinus1_1, FpCondToReg, + FpCtrlFromReg, + FpCtrlToReg, VfpuCtrlToReg, ZeroFpCond, diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 7f576dd4ea..561adf6f25 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -813,6 +813,17 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { case IROp::FpCondToReg: mips->r[inst->dest] = mips->fpcond; break; + case IROp::FpCtrlFromReg: + mips->fcr31 = mips->r[inst->src1] & 0x0181FFFF; + // Extract the new fpcond value. + // TODO: Is it really helping us to keep it separate? + mips->fpcond = (mips->fcr31 >> 23) & 1; + break; + case IROp::FpCtrlToReg: + // Update the fpcond bit first. + mips->fcr31 = (mips->fcr31 & ~(1 << 23)) | ((mips->fpcond & 1) << 23); + mips->r[inst->dest] = mips->fcr31; + break; case IROp::VfpuCtrlToReg: mips->r[inst->dest] = mips->vfpuCtrl[inst->src1]; break; diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 5725454841..a6c3b9b7f1 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -694,6 +694,12 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts out.Write(inst); } break; + case IROp::FpCtrlFromReg: + gpr.MapIn(inst.src1); + goto doDefault; + case IROp::FpCtrlToReg: + gpr.MapDirty(inst.dest); + goto doDefault; case IROp::Vec4Init: case IROp::Vec4Mov: From df313bd29696dd73ddffb6be0fab202dab7a66dc Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 25 Jul 2023 00:37:39 -0700 Subject: [PATCH 108/167] riscv: Fix rounding mode setting. --- Core/MIPS/IR/IRInst.cpp | 3 --- Core/MIPS/IR/IRPassSimplify.cpp | 5 +++-- Core/MIPS/RiscV/RiscVCompSystem.cpp | 34 +++++++++++++++++++++++++++++ Core/MIPS/RiscV/RiscVJit.cpp | 2 ++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 5723142a24..116908c656 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -115,9 +115,6 @@ static const IRMeta irMeta[] = { { IROp::FpCtrlFromReg, "FpCtrlFromReg", "_G" }, { IROp::FpCtrlToReg, "FpCtrlToReg", "G" }, { IROp::VfpuCtrlToReg, "VfpuCtrlToReg", "GI" }, - { IROp::RestoreRoundingMode, "RestoreRoundingMode", "" }, - { IROp::ApplyRoundingMode, "ApplyRoundingMode", "" }, - { IROp::UpdateRoundingMode, "UpdateRoundingMode", "" }, { IROp::SetCtrlVFPU, "SetCtrlVFPU", "TC" }, { IROp::SetCtrlVFPUReg, "SetCtrlVFPUReg", "TG" }, { IROp::SetCtrlVFPUFReg, "SetCtrlVFPUFReg", "TF" }, diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index a6c3b9b7f1..52ea66c5d4 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -695,10 +695,11 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts } break; case IROp::FpCtrlFromReg: - gpr.MapIn(inst.src1); + gpr.MapDirtyIn(IRREG_FCR31, inst.src1); + gpr.MapDirty(IRREG_FPCOND); goto doDefault; case IROp::FpCtrlToReg: - gpr.MapDirty(inst.dest); + gpr.MapDirtyInIn(inst.dest, IRREG_FPCOND, IRREG_FCR31); goto doDefault; case IROp::Vec4Init: diff --git a/Core/MIPS/RiscV/RiscVCompSystem.cpp b/Core/MIPS/RiscV/RiscVCompSystem.cpp index 8a8b995f5f..44dee67236 100644 --- a/Core/MIPS/RiscV/RiscVCompSystem.cpp +++ b/Core/MIPS/RiscV/RiscVCompSystem.cpp @@ -115,6 +115,40 @@ void RiscVJit::CompIR_Transfer(IRInst inst) { gpr.SetImm(IRREG_FPCOND, 0); break; + case IROp::FpCtrlFromReg: + gpr.MapDirtyIn(IRREG_FPCOND, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); + LI(SCRATCH1, 0x0181FFFF); + AND(SCRATCH1, gpr.R(inst.src1), SCRATCH1); + // Extract the new fpcond value. + if (cpu_info.RiscV_Zbs) { + BEXTI(gpr.R(IRREG_FPCOND), SCRATCH1, 23); + } else { + SRLI(gpr.R(IRREG_FPCOND), SCRATCH1, 23); + ANDI(gpr.R(IRREG_FPCOND), gpr.R(IRREG_FPCOND), 1); + } + SW(SCRATCH1, CTXREG, IRREG_FCR31 * 4); + break; + + case IROp::FpCtrlToReg: + gpr.MapDirtyIn(inst.dest, IRREG_FPCOND, MapType::AVOID_LOAD_MARK_NORM32); + // Load fcr31 and clear the fpcond bit. + LW(SCRATCH1, CTXREG, IRREG_FCR31 * 4); + if (cpu_info.RiscV_Zbs) { + BCLRI(SCRATCH1, SCRATCH1, 23); + } else { + LI(SCRATCH2, ~(1 << 23)); + AND(SCRATCH1, SCRATCH1, SCRATCH2); + } + + // Now get the correct fpcond bit. + ANDI(SCRATCH2, gpr.R(IRREG_FPCOND), 1); + SLLI(SCRATCH2, SCRATCH2, 23); + OR(gpr.R(inst.dest), SCRATCH1, SCRATCH2); + + // Also update mips->fcr31 while we're here. + SW(gpr.R(inst.dest), CTXREG, IRREG_FCR31 * 4); + break; + case IROp::VfpuCtrlToReg: gpr.MapDirtyIn(inst.dest, IRREG_VFPU_CTRL_BASE + inst.src1); MV(gpr.R(inst.dest), gpr.R(IRREG_VFPU_CTRL_BASE + inst.src1)); diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index b38aeb5124..720fd23d5f 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -291,6 +291,8 @@ void RiscVJit::CompileIRInst(IRInst inst) { case IROp::SetCtrlVFPUFReg: case IROp::FpCondToReg: case IROp::ZeroFpCond: + case IROp::FpCtrlFromReg: + case IROp::FpCtrlToReg: case IROp::VfpuCtrlToReg: case IROp::FMovFromGPR: case IROp::FMovToGPR: From 4e17c59cc2723c2f453c42e4efa9cb406b25e9d8 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 25 Jul 2023 19:11:27 -0700 Subject: [PATCH 109/167] riscv: Implement simple vec4 ops via floats. --- Core/MIPS/RiscV/RiscVCompLoadStore.cpp | 4 +-- Core/MIPS/RiscV/RiscVCompVec.cpp | 37 +++++++++++++++++++++++++- Core/MIPS/RiscV/RiscVRegCacheFPU.cpp | 26 ++++++++++++++++++ Core/MIPS/RiscV/RiscVRegCacheFPU.h | 3 +++ 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp index a1e0ae08c4..a543236610 100644 --- a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp +++ b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp @@ -125,7 +125,7 @@ void RiscVJit::CompIR_LoadShift(IRInst inst) { case IROp::Load32Left: case IROp::Load32Right: // Should not happen if the pass to split is active. - CompIR_Generic(inst); + DISABLE; break; default: @@ -256,7 +256,7 @@ void RiscVJit::CompIR_StoreShift(IRInst inst) { case IROp::Store32Left: case IROp::Store32Right: // Should not happen if the pass to split is active. - CompIR_Generic(inst); + DISABLE; break; default: diff --git a/Core/MIPS/RiscV/RiscVCompVec.cpp b/Core/MIPS/RiscV/RiscVCompVec.cpp index ac54192dd9..35dcd06b4e 100644 --- a/Core/MIPS/RiscV/RiscVCompVec.cpp +++ b/Core/MIPS/RiscV/RiscVCompVec.cpp @@ -55,13 +55,48 @@ void RiscVJit::CompIR_VecArith(IRInst inst) { switch (inst.op) { case IROp::Vec4Add: + fpr.Map4DirtyInIn(inst.dest, inst.src1, inst.src2); + for (int i = 0; i < 4; ++i) + FADD(32, fpr.R(inst.dest + i), fpr.R(inst.src1 + i), fpr.R(inst.src2 + i)); + break; + case IROp::Vec4Sub: + fpr.Map4DirtyInIn(inst.dest, inst.src1, inst.src2); + for (int i = 0; i < 4; ++i) + FSUB(32, fpr.R(inst.dest + i), fpr.R(inst.src1 + i), fpr.R(inst.src2 + i)); + break; + case IROp::Vec4Mul: + fpr.Map4DirtyInIn(inst.dest, inst.src1, inst.src2); + for (int i = 0; i < 4; ++i) + FMUL(32, fpr.R(inst.dest + i), fpr.R(inst.src1 + i), fpr.R(inst.src2 + i)); + break; + case IROp::Vec4Div: + fpr.Map4DirtyInIn(inst.dest, inst.src1, inst.src2); + for (int i = 0; i < 4; ++i) + FDIV(32, fpr.R(inst.dest + i), fpr.R(inst.src1 + i), fpr.R(inst.src2 + i)); + break; + case IROp::Vec4Scale: + fpr.SpillLock(inst.src2); + fpr.MapReg(inst.src2); + fpr.Map4DirtyIn(inst.dest, inst.src1); + fpr.ReleaseSpillLock(inst.src2); + for (int i = 0; i < 4; ++i) + FMUL(32, fpr.R(inst.dest + i), fpr.R(inst.src1 + i), fpr.R(inst.src2)); + break; + case IROp::Vec4Neg: + fpr.Map4DirtyIn(inst.dest, inst.src1); + for (int i = 0; i < 4; ++i) + FNEG(32, fpr.R(inst.dest + i), fpr.R(inst.src1 + i)); + break; + case IROp::Vec4Abs: - CompIR_Generic(inst); + fpr.Map4DirtyIn(inst.dest, inst.src1); + for (int i = 0; i < 4; ++i) + FABS(32, fpr.R(inst.dest + i), fpr.R(inst.src1 + i)); break; default: diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp index f4ddba03ad..3277bcda89 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp @@ -211,6 +211,32 @@ void RiscVRegCacheFPU::MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, ReleaseSpillLock(rt); } +void RiscVRegCacheFPU::Map4DirtyIn(IRRegIndex rdbase, IRRegIndex rsbase, bool avoidLoad) { + for (int i = 0; i < 4; ++i) + SpillLock(rdbase + i, rsbase + i); + bool load = !avoidLoad || (rdbase < rsbase + 4 && rdbase + 4 > rsbase); + for (int i = 0; i < 4; ++i) + MapReg(rdbase + i, load ? MIPSMap::DIRTY : MIPSMap::NOINIT); + for (int i = 0; i < 4; ++i) + MapReg(rsbase + i); + for (int i = 0; i < 4; ++i) + ReleaseSpillLock(rdbase + i, rsbase + i); +} + +void RiscVRegCacheFPU::Map4DirtyInIn(IRRegIndex rdbase, IRRegIndex rsbase, IRRegIndex rtbase, bool avoidLoad) { + for (int i = 0; i < 4; ++i) + SpillLock(rdbase + i, rsbase + i, rtbase + i); + bool load = !avoidLoad || (rdbase < rsbase + 4 && rdbase + 4 > rsbase) || (rdbase < rtbase + 4 && rdbase + 4 > rtbase); + for (int i = 0; i < 4; ++i) + MapReg(rdbase + i, load ? MIPSMap::DIRTY : MIPSMap::NOINIT); + for (int i = 0; i < 4; ++i) + MapReg(rsbase + i); + for (int i = 0; i < 4; ++i) + MapReg(rtbase + i); + for (int i = 0; i < 4; ++i) + ReleaseSpillLock(rdbase + i, rsbase + i, rtbase + i); +} + void RiscVRegCacheFPU::FlushRiscVReg(RiscVReg r) { _dbg_assert_(r >= F0 && r <= F31); int reg = r - F0; diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.h b/Core/MIPS/RiscV/RiscVRegCacheFPU.h index c861c6599b..934e312ca7 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.h +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.h @@ -64,6 +64,9 @@ public: void MapInIn(IRRegIndex rd, IRRegIndex rs); void MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad = true); void MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad = true); + void Map4Dirty(IRRegIndex rdbase, bool avoidLoad = true); + void Map4DirtyIn(IRRegIndex rdbase, IRRegIndex rsbase, bool avoidLoad = true); + void Map4DirtyInIn(IRRegIndex rdbase, IRRegIndex rsbase, IRRegIndex rtbase, bool avoidLoad = true); void FlushAll(); void FlushR(IRRegIndex r); void DiscardR(IRRegIndex r); From 23e9dffc6809943d3a9b7b3f70a208e2a9728bfe Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 25 Jul 2023 19:12:00 -0700 Subject: [PATCH 110/167] riscv: Implement vec4 shuffle and init. --- Core/MIPS/RiscV/RiscVCompSystem.cpp | 1 + Core/MIPS/RiscV/RiscVCompVec.cpp | 81 ++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/RiscV/RiscVCompSystem.cpp b/Core/MIPS/RiscV/RiscVCompSystem.cpp index 44dee67236..2413bb55c3 100644 --- a/Core/MIPS/RiscV/RiscVCompSystem.cpp +++ b/Core/MIPS/RiscV/RiscVCompSystem.cpp @@ -55,6 +55,7 @@ void RiscVJit::CompIR_Basic(IRInst inst) { FCVT(FConv::S, FConv::W, fpr.R(inst.dest), R_ZERO); } else { // TODO: In the future, could use FLI if it's approved. + // Also, is FCVT faster? LI(SCRATCH1, (int32_t)inst.constant); FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1); } diff --git a/Core/MIPS/RiscV/RiscVCompVec.cpp b/Core/MIPS/RiscV/RiscVCompVec.cpp index 35dcd06b4e..2e06dee3e6 100644 --- a/Core/MIPS/RiscV/RiscVCompVec.cpp +++ b/Core/MIPS/RiscV/RiscVCompVec.cpp @@ -39,9 +39,88 @@ void RiscVJit::CompIR_VecAssign(IRInst inst) { switch (inst.op) { case IROp::Vec4Init: + for (int i = 0; i < 4; ++i) + fpr.SpillLock(inst.dest + i); + for (int i = 0; i < 4; ++i) + fpr.MapReg(inst.dest + i, MIPSMap::NOINIT); + for (int i = 0; i < 4; ++i) + fpr.ReleaseSpillLock(inst.dest + i); + + // TODO: Check if FCVT/FMV/FL is better. + switch ((Vec4Init)inst.src1) { + case Vec4Init::AllZERO: + for (int i = 0; i < 4; ++i) + FCVT(FConv::S, FConv::W, fpr.R(inst.dest + i), R_ZERO); + break; + + case Vec4Init::AllONE: + LI(SCRATCH1, 1.0f); + FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1); + for (int i = 1; i < 4; ++i) + FMV(32, fpr.R(inst.dest + i), fpr.R(inst.dest)); + break; + + case Vec4Init::AllMinusONE: + LI(SCRATCH1, -1.0f); + FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1); + for (int i = 1; i < 4; ++i) + FMV(32, fpr.R(inst.dest + i), fpr.R(inst.dest)); + break; + + case Vec4Init::Set_1000: + LI(SCRATCH1, 1.0f); + for (int i = 0; i < 4; ++i) { + if (i == 0) + FMV(FMv::W, FMv::X, fpr.R(inst.dest + i), SCRATCH1); + else + FCVT(FConv::S, FConv::W, fpr.R(inst.dest + i), R_ZERO); + } + break; + + case Vec4Init::Set_0100: + LI(SCRATCH1, 1.0f); + for (int i = 0; i < 4; ++i) { + if (i == 1) + FMV(FMv::W, FMv::X, fpr.R(inst.dest + i), SCRATCH1); + else + FCVT(FConv::S, FConv::W, fpr.R(inst.dest + i), R_ZERO); + } + break; + + case Vec4Init::Set_0010: + LI(SCRATCH1, 1.0f); + for (int i = 0; i < 4; ++i) { + if (i == 2) + FMV(FMv::W, FMv::X, fpr.R(inst.dest + i), SCRATCH1); + else + FCVT(FConv::S, FConv::W, fpr.R(inst.dest + i), R_ZERO); + } + break; + + case Vec4Init::Set_0001: + LI(SCRATCH1, 1.0f); + for (int i = 0; i < 4; ++i) { + if (i == 3) + FMV(FMv::W, FMv::X, fpr.R(inst.dest + i), SCRATCH1); + else + FCVT(FConv::S, FConv::W, fpr.R(inst.dest + i), R_ZERO); + } + break; + } + break; + case IROp::Vec4Shuffle: + fpr.Map4DirtyIn(inst.dest, inst.src1); + for (int i = 0; i < 4; ++i) { + int lane = (inst.src2 >> (i * 2)) & 3; + FMV(32, fpr.R(inst.dest + i), fpr.R(inst.src1 + lane)); + } + break; + case IROp::Vec4Mov: - CompIR_Generic(inst); + fpr.Map4DirtyIn(inst.dest, inst.src1); + for (int i = 0; i < 4; ++i) + FMV(32, fpr.R(inst.dest + i), fpr.R(inst.src1 + i)); break; default: From 0c9dce8ba8f1348be446c5abb7102fbf18ea4284 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 25 Jul 2023 19:12:17 -0700 Subject: [PATCH 111/167] riscv: Implement vec4 dot. --- Core/MIPS/RiscV/RiscVCompVec.cpp | 34 +++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/RiscV/RiscVCompVec.cpp b/Core/MIPS/RiscV/RiscVCompVec.cpp index 2e06dee3e6..30193b950e 100644 --- a/Core/MIPS/RiscV/RiscVCompVec.cpp +++ b/Core/MIPS/RiscV/RiscVCompVec.cpp @@ -189,7 +189,39 @@ void RiscVJit::CompIR_VecHoriz(IRInst inst) { switch (inst.op) { case IROp::Vec4Dot: - CompIR_Generic(inst); + // TODO: Maybe some option to call the slow accurate mode? + fpr.SpillLock(inst.dest); + for (int i = 0; i < 4; ++i) { + fpr.SpillLock(inst.src1 + i); + fpr.SpillLock(inst.src2 + i); + } + for (int i = 0; i < 4; ++i) { + fpr.MapReg(inst.src1 + i); + fpr.MapReg(inst.src2 + i); + } + fpr.MapReg(inst.dest, MIPSMap::NOINIT); + for (int i = 0; i < 4; ++i) { + fpr.ReleaseSpillLock(inst.src1 + i); + fpr.ReleaseSpillLock(inst.src2 + i); + } + fpr.ReleaseSpillLock(inst.dest); + + if ((inst.dest < inst.src1 + 4 && inst.dest >= inst.src1) || (inst.dest < inst.src2 + 4 && inst.dest >= inst.src2)) { + // This means inst.dest overlaps one of src1 or src2. We have to do that one first. + // Technically this may impact -0.0 and such, but dots accurately need to be aligned anyway. + for (int i = 0; i < 4; ++i) { + if (inst.dest == inst.src1 + i || inst.dest == inst.src2 + i) + FMUL(32, fpr.R(inst.dest), fpr.R(inst.src1 + i), fpr.R(inst.src2 + i)); + } + for (int i = 0; i < 4; ++i) { + if (inst.dest != inst.src1 + i && inst.dest != inst.src2 + i) + FMADD(32, fpr.R(inst.dest), fpr.R(inst.src1 + i), fpr.R(inst.src2 + i), fpr.R(inst.dest)); + } + } else { + FMUL(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2)); + for (int i = 1; i < 4; ++i) + FMADD(32, fpr.R(inst.dest), fpr.R(inst.src1 + i), fpr.R(inst.src2 + i), fpr.R(inst.dest)); + } break; default: From 5122b0c78e2fab9b3128661e10f634c9f7cff79e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 25 Jul 2023 19:58:31 -0700 Subject: [PATCH 112/167] riscv: Cleanup unnecessary fcr31 func. Don't need this, we use DYNAMIC. --- Core/MIPS/RiscV/RiscVJit.cpp | 8 -------- Core/MIPS/RiscV/RiscVJit.h | 1 - 2 files changed, 9 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 720fd23d5f..b94369c4ca 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -498,19 +498,11 @@ void RiscVJit::ClearCache() { memset(blockStartAddrs_, 0, sizeof(blockStartAddrs_[0]) * MAX_ALLOWED_JIT_BLOCKS); } -void RiscVJit::UpdateFCR31() { - IRJit::UpdateFCR31(); - - // TODO: Handle rounding modes? -} - void RiscVJit::RestoreRoundingMode(bool force) { - // TODO: Could maybe skip if not hasSetRounding? But that's on IRFrontend... FSRMI(Round::NEAREST_EVEN); } void RiscVJit::ApplyRoundingMode(bool force) { - // TODO: Also could maybe sometimes skip? QuickCallFunction(applyRoundingMode_); } diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index 105090d9d7..02c90c735d 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -42,7 +42,6 @@ public: const u8 *GetCrashHandler() const override; void ClearCache() override; - void UpdateFCR31() override; // TODO: GetBlockCacheDebugInterface, block linking? From ce45c943be8970fb0b36c71d5ddc887a62d55f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 26 Jul 2023 07:28:25 +0200 Subject: [PATCH 113/167] Bump naett with fix for build problem on newer NDK See #17583 --- ext/naett | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/naett b/ext/naett index 3a4aa1283f..978500f0bc 160000 --- a/ext/naett +++ b/ext/naett @@ -1 +1 @@ -Subproject commit 3a4aa1283f357cc7b00b9f1019fef5f8e9f3bda2 +Subproject commit 978500f0bc2d3a5e2a235e27826af6166016fea7 From 4789656b331bbd9efb1fae990913720105d759d8 Mon Sep 17 00:00:00 2001 From: Jaakko Saarikko <74954275+styxnix@users.noreply.github.com> Date: Wed, 26 Jul 2023 22:44:55 +0300 Subject: [PATCH 114/167] Update fi_FI.ini Translation work on Finnish language. --- assets/lang/fi_FI.ini | 252 +++++++++++++++++++++--------------------- 1 file changed, 126 insertions(+), 126 deletions(-) diff --git a/assets/lang/fi_FI.ini b/assets/lang/fi_FI.ini index 949d525c02..c05d06318f 100644 --- a/assets/lang/fi_FI.ini +++ b/assets/lang/fi_FI.ini @@ -1,42 +1,42 @@ [Achievements] %1: Leaderboard attempt failed = %1: Leaderboard attempt failed %1: Leaderboard attempt started = %1: Leaderboard attempt started -%d achievements, %d points = %d achievements, %d points -Account = Account -Achievement Unlocked = Achievement Unlocked -Achievements = Achievements -Achievements are disabled = Achievements are disabled -Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now -Challenge Mode = Challenge Mode -Challenge Mode (no savestates) = Challenge Mode (no savestates) -Contacting RetroAchievements server... = Contacting RetroAchievements server... -Customize = Customize -Earned = You have earned %d of %d achievements, and %d of %d points +%d achievements, %d points = %d saavutusta, %d pistettä +Account = Tili +Achievement Unlocked = Saavutus avattu +Achievements = Saavutukset +Achievements are disabled = Saavutukset ovat pois käytöstä +Can't log in to RetroAchievements right now = RetroAchievements:iin ei voida kirjautua juuri nyt +Challenge Mode = Haastetila +Challenge Mode (no savestates) = Haastetila (ei tilatallennuksia) +Contacting RetroAchievements server... = Yhdistetään RetroAchievements:in palvelimelle... +Customize = Mukauta +Earned = Olet ansainnut %d/%d saavutusta, ja %d/%d pistettä Encore Mode = Encore Mode -How to use RetroAchievements = How to use RetroAchievements +How to use RetroAchievements = Kuinka käyttää RetroAchievements:ia In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below -Leaderboard score submission = Leaderboard score submission -Leaderboard submission is enabled = Leaderboard submission is enabled -Leaderboards = Leaderboards -Links = Links -Locked achievements = Locked achievements -Log bad memory accesses = Log bad memory accesses +Leaderboard score submission = Sijoituslistan pisteiden lähetys +Leaderboard submission is enabled = Sijoituslistan lähetys on käytössä +Leaderboards = Sijoituslista +Links = Linkit +Locked achievements = Lukitut saavutukset +Log bad memory accesses = Kirjaa virheelliset muistipääsyt Mastered %1 = Mastered %1 -Register on www.retroachievements.org = Register on www.retroachievements.org -RetroAchievements are not available for this game = RetroAchievements are not available for this game -RetroAchievements website = RetroAchievements website +Register on www.retroachievements.org = Rekisteröidy osoitteessa www.retroachievements.org +RetroAchievements are not available for this game = RetroAchievements eivät ole käytössä tälle pelille +RetroAchievements website = RetroAchievements verkkosivu Rich Presence = Rich Presence -Save state loaded without achievement data = Save state loaded without achievement data -Save states not available in Challenge Mode = Save states not available in Challenge Mode -Sound Effects = Sound Effects -Statistics = Statistics +Save state loaded without achievement data = Tilatallennus ladattu, ilman saavutuksen tietoja +Save states not available in Challenge Mode = Tilatallennukset eivät ole käytössä haastetilassa +Sound Effects = Äänimerkit +Statistics = Tilastot Submitted %1 for %2 = Submitted %1 for %2 -Syncing achievements data... = Syncing achievements data... -Test Mode = Test Mode -This feature is not available in Challenge Mode = This feature is not available in Challenge Mode -This game has no achievements = This game has no achievements -Unlocked achievements = Unlocked achievements -Unofficial achievements = Unofficial achievements +Syncing achievements data... = Synkoronoidaan saavutusten tietoja... +Test Mode = Testitila +This feature is not available in Challenge Mode = Tämä ominaisuus ei ole käytettävissä haastetilassa +This game has no achievements = Tällä pelillä ei ole saavutuksia +Unlocked achievements = Avatut saavutukset +Unofficial achievements = Epäviralliset saavutukset [Audio] Alternate speed volume = Vaihtoehtoisen nopeuden äänenvoimakkuus @@ -58,92 +58,92 @@ Use global volume = Käytä yleistä äänenvoimakkuutta WASAPI (fast) = WASAPI (nopea) [Controls] -Analog Binding = Analog Binding -Analog Limiter = Analog limiter -Analog Settings = Analog Settings -Analog Stick = Analog stick -Analog Style = Analog Style -AnalogLimiter Tip = When the analog limiter button is pressed -Auto = Auto -Auto-centering analog stick = Auto-centering analog stick -Auto-hide buttons after delay = Auto-hide buttons after delay +Analog Binding = Analoginen kartoitus +Analog Limiter = Analoginen rajoitin +Analog Settings = Analogisen asetukset +Analog Stick = Analoginen sauva +Analog Style = Analogisen tyyli +AnalogLimiter Tip = Kun analogisen rajoittimen näppäintä painetaan +Auto = Automaattinen +Auto-centering analog stick = Automaattisesti keskittävä analoginen sauva +Auto-hide buttons after delay = Piilota näppäimet automaattisesti viiveen jälkeen Auto-rotation speed = Auto-rotation speed -Binds = Binds -Button Binding = Button Binding -Button Opacity = Painikkeiden peittävyys -Button style = Button style -Calibrate Analog Stick = Calibrate Analog Stick -Calibrate = Calibrate -Calibrated = Calibrated -Calibration = Calibration +Binds = Sidokset +Button Binding = Näppäinten sidokset +Button Opacity = Näppäinten peittävyys +Button style = Näppäinten tyyli +Calibrate Analog Stick = Kalibroi analoginen sauva +Calibrate = Kalibroi +Calibrated = Kalibroitu +Calibration = Kalibraatio Circular low end radius = Circular low end radius Circular stick input = Circular stick input Classic = Klassinen -Confine Mouse = Trap mouse within window/display area -Control Mapping = Control mapping +Confine Mouse = Kaappaa hiiri ikkunan/näytön sisälle +Control Mapping = Ohjauksen kartoitus Custom Key Setting = Custom Key Setting -Customize = Customize -Customize Touch Controls = Edit touch control layout... -D-PAD = D-Pad -Deadzone radius = Deadzone radius -Disable D-Pad diagonals (4-way touch) = Disable D-Pad diagonals (4-way touch) -Disable diagonal input = Disable diagonal input -Double tap = Double tap -Enable gesture control = Enable gesture control +Customize = Mukauta +Customize Touch Controls = Muokkaa kosketusohjauksen asetelmaa... +D-PAD = Ristiohjain +Deadzone radius = Katvealueen säde +Disable D-Pad diagonals (4-way touch) = Poista käytöstä ristiohjaimen vinottainen syöte (nelisuuntainen syöte) +Disable diagonal input = Poista vinottainen syöte käytöstä +Double tap = Kaksinkertainen napautus +Enable gesture control = Ota eleohjaukset käyttöön Enable standard shortcut keys = Enable standard shortcut keys -frames = frames -Gesture = Gesture -Gesture mapping = Gesture mapping -Glowing borders = Glowing borders -HapticFeedback = Haptic feedback (vibration) -Hide touch analog stick background circle = Hide touch analog stick background circle -Icon = Icon -Ignore gamepads when not focused = Ignore gamepads when not focused -Ignore Windows Key = Disable Windows key -Invert Axes = Invert axes -Invert Tilt along X axis = Invert tilt along X axis -Invert Tilt along Y axis = Invert tilt along Y axis +frames = kuvat +Gesture = Eleet +Gesture mapping = Eleiden kartoitus +Glowing borders = Hohtavat rajat +HapticFeedback = Haptinen palaute (värinä) +Hide touch analog stick background circle = Piiloita kosketettavan analogisen sauvan taustaympyrä +Icon = Kuvake +Ignore gamepads when not focused = Älä huomioi ohjaimia, kun ohjelma ei keskitettynä +Ignore Windows Key = Poista Windows-näppäin käytöstä +Invert Axes = Käännä akselit +Invert Tilt along X axis = Käännä kallistus X-akselilla +Invert Tilt along Y axis = Käännä kallistus Y-akselilla Keep this button pressed when right analog is pressed = Keep this button pressed when right analog is pressed -Keyboard = Keyboard control settings -L/R Trigger Buttons = L/R trigger buttons +Keyboard = Näppäimistöohjauksen asetukset +L/R Trigger Buttons = L/R-liipaisimen näppäimet Landscape = Landscape Landscape Auto = Landscape auto Landscape Reversed = Landscape reversed Low end radius = Low end radius Mouse = Mouse settings -Mouse sensitivity = Mouse sensitivity -Mouse smoothing = Mouse smoothing +Mouse sensitivity = Hiiren herkkyys +Mouse smoothing = Hiiren tasoitus MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. None (Disabled) = None (disabled) -Off = Off +Off = Pois OnScreen = Kosketusnäytön kontrollit Portrait = Portrait Portrait Reversed = Portrait reversed -PSP Action Buttons = PSP action buttons +PSP Action Buttons = PSP-toiminta näppäimet Rapid fire interval = Rapid fire interval Raw input = Raw input Repeat mode = Repeat mode -Reset to defaults = Reset to defaults -Screen Rotation = Screen rotation -Sensitivity (scale) = Sensitivity (scale) -Sensitivity = Sensitivity +Reset to defaults = Palauta oletukset +Screen Rotation = Näytön kierto +Sensitivity (scale) = Herkkyys (skaala) +Sensitivity = Herkkyys Shape = Shape Show Touch Pause Menu Button = Show pause menu button -Swipe = Swipe -Swipe sensitivity = Swipe sensitivity -Swipe smoothing = Swipe smoothing -Thin borders = Thin borders -Tilt control setup = Tilt control setup -Tilt Input Type = Tilt input type -Tilt Sensitivity along X axis = Tilt sensitivity along X axis -Tilt Sensitivity along Y axis = Tilt sensitivity along Y axis -To Calibrate = Hold device at your preferred angle and press "Calibrate". -Toggle mode = Toggle mode -Touch Control Visibility = Touch control visibility -Use custom right analog = Use custom right analog -Use Mouse Control = Use mouse control -Visibility = Visibility -Visible = Visible +Swipe = Pyyhkäise +Swipe sensitivity = Pyyhkäisyn herkkyys +Swipe smoothing = Pyyhkäisyn tasoitus +Thin borders = Ohuet rajat +Tilt control setup = Kallistusohjauksen asetus +Tilt Input Type = Kallistusohjauksen tyyppi +Tilt Sensitivity along X axis = Kallistuksen herkkyys X-akselilla +Tilt Sensitivity along Y axis = Kallistuksen herkkyys Y-akselilla +To Calibrate = Pidä laitettasi halutussa kulmassa, ja paina 'Kalibroi'. +Toggle mode = Vaihto tila +Touch Control Visibility = Kosketusohjauksen näkyvyys +Use custom right analog = Käytä mukautettua oikeaa analogista +Use Mouse Control = Käytä hiiriohjausta +Visibility = Näkyvyys +Visible = Näkyvä X = X Y = Y @@ -718,7 +718,7 @@ Hold = Hold Home = Home L = L Left = Dpad Left -Load State = Load State +Load State = Lataa tila Mute toggle = Mute toggle Next Slot = Next Slot None = None @@ -738,7 +738,7 @@ RightAn.Right = RightAn.Right RightAn.Up = RightAn.Up Rotate Analog (CCW) = Rotate analog (CCW) Rotate Analog (CW) = Rotate analog (CW) -Save State = Save State +Save State = Tallena tila Screen = Screen Screenshot = Screenshot Select = Select @@ -860,36 +860,36 @@ WLAN Channel = WLAN channel You're in Offline Mode, go to lobby or online hall = You're in offline mode, go to lobby or online hall [PSPSettings] -Auto = Auto -Chinese (simplified) = Chinese (simplified) -Chinese (traditional) = Chinese (traditional) -Dutch = Dutch -English = English -French = French -Game language = Game language -German = German -Italian = Italian -Japanese = Japanese -Korean = Korean -Games often don't support all languages = Games often don't support all languages -Portuguese = Portuguese -Russian = Russian -Spanish = Spanish +Auto = Automaattinen +Chinese (simplified) = Kiina (yksinkertaistettu) +Chinese (traditional) = Kiina (perinteinen) +Dutch = Hollanti +English = Englanti +French = Ranska +Game language = Pelin kieli +German = Saksa +Italian = Italia +Japanese = Japani +Korean = Korea +Games often don't support all languages = Pelit eivät usein tue kaikkia kieliä +Portuguese = Portugali +Russian = Venäjä +Spanish = Espanja [Pause] Cheats = Huijaukset Continue = Jatka -Create Game Config = Create game config -Delete Game Config = Delete game config -Exit to menu = Exit to menu -Game Settings = Game settings -Load State = Lataa tilanne -Rewind = Rewind -Save State = Tallenna tilanne +Create Game Config = Luo pelin asetustiedosto +Delete Game Config = Poista pelin asetustiedosto +Exit to menu = Poistu valikkoon +Game Settings = Pelin asetukset +Load State = Lataa tila +Rewind = Takaisinkelaus +Save State = Tallenna tila Settings = Asetukset -Switch UMD = Switch UMD -Undo last load = Undo last load -Undo last save = Undo last save +Switch UMD = Vaihda UMD +Undo last load = Peru viimeisin tilan lataus +Undo last save = Peru viimeisin tilan tallennus [PostShaders] (duplicated setting, previous slider will be used) = (duplicated setting, previous slider will be used) @@ -934,7 +934,7 @@ VideoSmoothingAA = VideoSmoothingAA Vignette = Vignette [PSPCredits] -all the forum mods = all the forum mods +all the forum mods = Kaikki foorumin moderaattorit build server = build server Buy Gold = Buy Gold check = Also check out Dolphin, the best Wii/GC emu around: @@ -963,7 +963,7 @@ tools = Free tools used: # Add translators or contributors who translated PPSSPP into your language here. # Add translators1-6 for up to 6 lines of translator credits. # Leave extra lines blank. 4 contributors per line seems to look best. -translators1 = +translators1 = Jaakko Saarikko translators2 = translators3 = translators4 = @@ -1178,8 +1178,8 @@ Color Tint = Color Tint Error: load undo state is from a different game = Error: load undo state is from a different game Failed to load state for load undo. Error in the file system. = Failed to load state for load undo. Error in the file system. Floating symbols = Floating symbols -Game crashed = Game crashed -Language = Language +Game crashed = Peli kaatui +Language = Kieli Memory Stick folder = Memory Stick folder Memory Stick size = Memory Stick size Change Nickname = Vaihda nimimerkkiä From 92b6613adfcf91e657d6808dfeb7a6f82bdb0d70 Mon Sep 17 00:00:00 2001 From: Benjamin Benda Gates Date: Thu, 27 Jul 2023 18:42:56 +0700 Subject: [PATCH 115/167] Update th_TH.ini --- assets/lang/th_TH.ini | 180 ++++++++++++++++++++++++------------------ 1 file changed, 104 insertions(+), 76 deletions(-) diff --git a/assets/lang/th_TH.ini b/assets/lang/th_TH.ini index e63ba19d8e..97c3db01b8 100644 --- a/assets/lang/th_TH.ini +++ b/assets/lang/th_TH.ini @@ -1,42 +1,48 @@ [Achievements] -%1: Leaderboard attempt failed = %1: Leaderboard attempt failed -%1: Leaderboard attempt started = %1: Leaderboard attempt started -%d achievements, %d points = %d achievements, %d points -Account = Account -Achievement Unlocked = Achievement Unlocked -Achievements = Achievements -Achievements are disabled = Achievements are disabled -Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now -Challenge Mode = Challenge Mode -Challenge Mode (no savestates) = Challenge Mode (no savestates) -Contacting RetroAchievements server... = Contacting RetroAchievements server... -Customize = Customize -Earned = You have earned %d of %d achievements, and %d of %d points -Encore Mode = Encore Mode -How to use RetroAchievements = How to use RetroAchievements -In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below -Leaderboard score submission = Leaderboard score submission -Leaderboard submission is enabled = Leaderboard submission is enabled -Leaderboards = Leaderboards -Links = Links -Locked achievements = Locked achievements -Log bad memory accesses = Log bad memory accesses -Mastered %1 = Mastered %1 -Register on www.retroachievements.org = Register on www.retroachievements.org -RetroAchievements are not available for this game = RetroAchievements are not available for this game -RetroAchievements website = RetroAchievements website +%1: Leaderboard attempt failed = %1: ส่งข้อมูลไปยังกระดานแต้มผู้นำล้มเหลว +%1: Leaderboard attempt started = %1: เริ่มการส่งข้อมูลไปยังกระดานแต้มผู้นำแล้ว +%d achievements, %d points = %d เป้าหมายความสำเร็จ, %d แต้ม +Account = บัญชีผู้ใช้งาน +Achievement unlocked = ตอนเป้าหมายความสำเร็จถูกปลดล็อค +Achievements = เป้าหมายความสำเร็จ +Achievements enabled = เปิดใช้งาน เป้าหมายความสำเร็จ +Achievements are disabled = ปิดใช้งาน เป้าหมายความสำเร็จ +Can't log in to RetroAchievements right now = ไม่สามารถล็อกอินเข้าไปยัง RetroAchievements ได้ในขณะนี้ +Challenge Mode = โหมดท้าทาย +Challenge Mode (no savestates) = โหมดท้าทาย (ไม่มีเซฟสเตท) +Contacting RetroAchievements server... = กำลังเชื่อมต่อกับเซิร์ฟเวอร์ RetroAchievements... +Customize = ปรับแต่ง +Earned = คุณได้รับ %d of %d เป้าหมายความสำเร็จ, และ %d ของ %d แต้ม +Encore Mode = โหมดคำราม +Failed logging in to RetroAchievements = ล้มเหลวในการเข้าใช้งานไปยัง RetroAchievements +How to use RetroAchievements = วิธีใช้งาน RetroAchievements +In Encore mode - listings may be wrong below = ในโหมดคำราม - รายชื่อด้านล่างอาจจะผิด +Leaderboard score submission = ตอนส่งคะแนนไปยังกระดานแต้มผู้นำ +Leaderboard submission is enabled = เปิดใช้งานการส่งคะแนนไปยังกระดานแต้มผู้นำ +Leaderboards = กระดานแต้มผู้นำ +Links = ลิงก์ +Locked achievements = เป้าหมายความสำเร็จที่ยังถูกล็อคเอาไว้ +Log bad memory accesses = เก็บค่าการเข้าถึงหน่วยความจำที่ไม่ดี +Log in = เข้าใช้งาน +Mastered %1 = เชี่ยวชาญ %1 +Register on www.retroachievements.org = สมัครได้ที่ www.retroachievements.org +RetroAchievements are not available for this game = ไม่สามารถใช้งาน RetroAchievements กับเกมนี้ได้ +RetroAchievements website = เว็บไซต์ RetroAchievements Rich Presence = Rich Presence -Save state loaded without achievement data = Save state loaded without achievement data -Save states not available in Challenge Mode = Save states not available in Challenge Mode -Sound Effects = Sound Effects -Statistics = Statistics -Submitted %1 for %2 = Submitted %1 for %2 -Syncing achievements data... = Syncing achievements data... -Test Mode = Test Mode -This feature is not available in Challenge Mode = This feature is not available in Challenge Mode -This game has no achievements = This game has no achievements -Unlocked achievements = Unlocked achievements -Unofficial achievements = Unofficial achievements +Save state loaded without achievement data = โหลดเซฟสเตทแล้วโดยที่ไม่มีข้อมูลเป้าหมายความสำเร็จ +Save states not available in Challenge Mode = ไม่สามารถใช้งานเซฟสเตทได้ในโหมดท้าทาย +Settings = การตั้งค่า +Sound effects = เสียงเอฟเฟ็คท์ +Sound Effects = เสียงเอฟเฟ็คท์ +Statistics = สถิติ +Submitted %1 for %2 = ส่ง %1 สำหรับ %2 +Syncing achievements data... = กำลังซิงก์ข้อมูล เป้าหมายความสำเร็จ... +Test Mode = โหมดทดสอบ +This feature is not available in Challenge Mode = ฟีเจอร์นี้ไม่สามารถปรับใช้งานได้ในโหมดท้าทาย +This game has no achievements = เกมนี้ไม่มีเป้าหมายความสำเร็จ +Unlocked achievements = เป้าหมายความสำเร็จที่ถูกปลดล็อคแล้ว +Unofficial achievements = เป้าหมายความสำเร็จอย่างไม่เป็นทางการ +Username = ชื่อผู้ใช้งาน [Audio] Alternate speed volume = ระดับเสียงในโหมดเร่งความเร็ว @@ -76,11 +82,11 @@ Calibrate Analog Stick = ปรับค่าความคลาดเคล Calibrate = สอบเทียบสมดุล Calibrated = ค่าความคลาดเคลื่อนที่ปรับ Calibration = ปรับค่าความคลาดเคลื่อน -Circular low end radius = Circular low end radius -Circular stick input = ค่านำเข้าหมุนปุ่ม +Circular low end radius = รัศมีต่ำสุดของการหมุนปุ่ม +Circular stick input = ค่านำเข้าการหมุนปุ่ม Classic = แบบดั้งเดิม Confine Mouse = จำกัดเมาส์ภายในหน้าต่าง/พื้นที่แสดงผล -Control Mapping = เปลี่ยนแปลงค่าควบคุม +Control Mapping = เปลี่ยนแปลงค่าการควบคุม Custom Key Setting = ติดตั้งปุ่มคอมโบปรับแต่ง Customize = ปรับแต่ง Customize Touch Controls = ปรับแต่งปุ่มควบคุมสัมผัสหน้าจอ @@ -91,7 +97,7 @@ Disable diagonal input = ปิดการใช้งานอนาล็อ Double tap = แตะสองครั้ง Enable gesture control = เปิดการควบคุมปัดหน้าจอสัมผัส Enable standard shortcut keys = เปิดการใช้งานปุ่มคีย์ลัดแบบพื้นฐาน -frames = frames +frames = เฟรม Gesture = ปัดจอ Gesture mapping = เซ็ตปุ่มควบคุมปัดหน้าจอสัมผัส Glowing borders = แบบขอบทึบแสง @@ -120,7 +126,7 @@ OnScreen = ปรับใช้หน้าจอควบคุมแบบป Portrait = แนวตั้ง Portrait Reversed = แนวตั้งกลับด้าน PSP Action Buttons = ปุ่มแอ็คชั่นของ PSP -Rapid fire interval = Rapid fire interval +Rapid fire interval = ปุ่มยิงรัวอย่างรวดเร็ว Raw input = ค่าตั้งต้น Repeat mode = โหมดปุ่มย้ำ Reset to defaults = คืนค่าเริ่มต้น @@ -148,7 +154,6 @@ X = แกน X Y = แกน Y [CwCheats] -#Add Cheat = เพิ่มสูตรโกง # This one is not used Cheats = การใช้สูตรโกง Edit Cheat File = ปรับแต่งไฟล์สูตรโกง Import Cheats = นำเข้าจากไฟล์สูตรโกง @@ -286,6 +291,7 @@ Log Dropped Frame Statistics = เก็บสถิติค่าเฟรม Log Level = เก็บค่าระดับ Log View = เก็บค่ามุมมอง Logging Channels = ช่องทางเก็บค่าข้อมูล +Multi-threaded rendering = การเรนเดอร์แบบมัลติเธรด Next = ถัดไป No block = ไม่ต้องบล็อค Prev = ถอยกลับ @@ -338,14 +344,14 @@ DeleteConfirmGameConfig = คุณแน่ใจนะว่าต้องก DeleteFailed = ไม่สามารถลบข้อมูลนี้ได้ Deleting = กำลังลบข้อมูล\nโปรดรอ... Disable All = ปิดใช้งานทั้งหมด -Disabled = Disabled +Disabled = ปิดการใช้งาน Dumps = ไฟล์ดั๊มพ์ Edit = แก้ไข Enable All = เปิดใช้งานทั้งหมด Enabled = Enabled Enter = ยืนยัน -Failed to connect to server, check your internet connection. = Failed to connect to server, check your internet connection. -Failed to log in, check your username and password. = Failed to log in, check your username and password. +Failed to connect to server, check your internet connection. = ล้มเหลวในการเชื่อมต่อกับเซิร์ฟเวอร์ โปรดเช็คการเชื่อมต่ออินเตอร์เน็ต +Failed to log in, check your username and password. = ล้มเหลวในการเข้าใช้งาน โปรดเช็ค ชื่อผู้ใช้งาน กับ รหัสผ่าน ดูก่อน Filter = ตัวกรอง Finish = สิ้นสุด GE Frame Dumps = ไฟล์กราฟิกเอนจิ้นเฟรมดั๊มพ์ @@ -356,10 +362,10 @@ Load = โหลด Load completed = โหลดข้อมูลสำเร็จ Loading = กำลังโหลด\nโปรดรอ... LoadingFailed = ไม่สามารถโหลดข้อมูลนี้ได้ -Log in = Log in -Log out = Log out -Logged in! = Logged in! -Logging in... = Logging in... +Log in = เข้าใช้งาน +Log out = ออกจากการใช้งาน +Logged in! = เข้าใช้งานแล้ว! +Logging in... = กำลังเข้าใช้งาน... Move = ย้าย Move Down = ย้ายลงล่าง Move Up = ย้ายขึ้นบน @@ -370,7 +376,7 @@ ObtainingIP = กำลังรับไอพี\nโปรดรอสัก OK = ตกลง Old savedata detected = ตรวจพบข้อมูลเซฟเก่า Options = ตัวเลือก -Password = Password +Password = รหัสผ่าน Remove = ลบออก Reset = รีเซ็ต Resize = ขนาด @@ -383,7 +389,7 @@ SavingFailed = ไม่สามารถบันทึกข้อมูล Search = ค้นหา seconds, 0:off = วินาที, 0 = ปิด Select = เลือก -Settings = Settings +Settings = การตั้งค่า Shift = เปลี่ยน Skip = ข้าม Snap = ขยับ @@ -395,7 +401,7 @@ There is no data = ไม่มีข้อมูล Toggle All = ทั้งหมด Toggle List = รายการ Unsupported = ไม่รองรับ -Username = Username +Username = ชื่อผู้ใช้งาน When you save, it will load on a PSP, but not an older PPSSPP = เซฟเกมของคุณนั้น สามารถนำไปโหลดผ่านบนเครื่อง PSP ได้\nแต่นำไปใช้กับ PPSSPP ที่เวอร์ชั่นเก่ากว่าไม่ได้ When you save, it will not work on outdated PSP Firmware anymore = เซฟเกมของคุณนั้น ไม่สามารถนำไปใช้กับเครื่อง PSP ที่ใช้เฟิร์มแวร์รุ่นเก่าได้ Yes = ใช่ @@ -639,12 +645,12 @@ Zip archive corrupt = ไฟล์ ZIP ไม่สมบูรณ์ (เสี Zip file does not contain PSP software = ไฟล์ ZIP นี้ ไม่มีข้อมูลซอฟแวร์ของเกม PSP [KeyMapping] -Allow combo mappings = Allow combo mappings +Allow combo mappings = เซ็ตปุมคอมโบ Autoconfigure = ค่าอัตโนมัติ Autoconfigure for device = กำหนดค่าให้อัตโนมัติสำหรับอุปกรณ์นี้ Bind All = เซ็ตปุ่มทั้งหมด Clear All = ล้างทั้งหมด -Combo mappings are not enabled = Combo mappings are not enabled +Combo mappings are not enabled = เซ็ตปุ่มคอมโบยังไม่ได้เปิดใช้งาน Default All = คืนค่าเดิมทั้งหมด Map a new key for = ปรับปุ่มใหม่สำหรับ Map Key = ตั้งค่าปุ่ม @@ -727,6 +733,7 @@ None = ไม่มี Note = หมายเหตุ OpenChat = เปิดแชท Pause = หยุดชั่วคราว +Previous Slot = ช่องสเตทอันที่แล้ว R = อาร์ RapidFire = กดรัวต่อเนื่อง Record = บันทึกหน้าจอ @@ -845,7 +852,7 @@ Quick Chat 3 = แชทด่วน 3 Quick Chat 4 = แชทด่วน 4 Quick Chat 5 = แชทด่วน 5 QuickChat = แชทด่วน -Randomize = Randomize +Randomize = กดสุ่มค่า Send = ส่ง Send Discord Presence information = แสดงชื่อเกมที่กำลังเล่นอยู่ไปยังแอพ Discord Top Center = มุมบนกลางจอ @@ -861,23 +868,24 @@ WLAN Channel = ช่องสัญญาณ WLAN You're in Offline Mode, go to lobby or online hall = คุณอยู่ในโหมดออฟไลน์ ต้องเข้าไปในล็อบบี้หรือออนไลน์ฮอลล์ก่อน [PSPSettings] -Auto = Auto -Chinese (simplified) = Chinese (simplified) -Chinese (traditional) = Chinese (traditional) -Dutch = Dutch -English = English -French = French -Game language = Game language -German = German -Italian = Italian -Japanese = Japanese -Korean = Korean -Games often don't support all languages = Games often don't support all languages -Portuguese = Portuguese -Russian = Russian -Spanish = Spanish +Auto = อัตโนมัติ +Chinese (simplified) = จีน (ตัวย่อ) +Chinese (traditional) = จีน (ตัวเต็ม) +Dutch = เนเธอร์แลนด์ +English = อังกฤษ +French = ฝรั่งเศส +Game language = ภาษาที่ใช้กับเกม +German = เยอรมัน +Italian = อิตาลี +Japanese = ญี่ปุ่น +Korean = เกาหลี +Games often don't support all languages = ในบางเกมอาจจะไม่รองรับทุกภาษา +Portuguese = โปรตุเกส +Russian = รัสเซีย +Spanish = สเปน [Pause] +Achievements = เป้าหมายความสำเร็จ Cheats = สูตรโกง Continue = ดำเนินการต่อ Create Game Config = สร้างการตั้งค่าเฉพาะเกม @@ -903,19 +911,19 @@ Animation speed (0 -> disable) = ความเร็วอนิเมชั Aspect = ทิศทาง Black border = ขอบดำ Bloom = ภาพสว่างขึ้น +BloomNoBlur = ภาพสว่างขึ้น (ไม่เบลอ) Brightness = แสงสว่าง Cartoon = ภาพออกแนวการ์ตูน -CatmullRom = ภาพอัพสเกลแบบไบคิวบิค (แคทมิว-รอม) ColorCorrection = ภาพสีสมจริง ColorPreservation = การเก็บรักษาสีเดิม Contrast = ความคมชัด CRT = ภาพออกโทนเหลืองขึ้นเส้น +FakeReclections = ภาพเงาสะท้อนลวงตา FXAA = ภาพเน้นลบขอบรอยหยัก Gamma = แกมม่า GreenLevel = ระดับสีเขียว Intensity = ความเข้มของแสง LCDPersistence = ภาพ LCD แบบค้างคา -MitchellNetravali = ภาพอัพสเกลแบบไบคิวบิค (มิทเชลล์-เนทราวาลิ) Natural = ภาพเน้นสีธรรมชาติ NaturalA = ภาพเน้นสีธรรมชาติ (ไม่เบลอ) Off = ปิด @@ -930,6 +938,7 @@ SSAA(Gauss) = ภาพเน้นความละเอียดให้ส Strength = ความเด่นชัด Tex4xBRZ = 4xBRZ TexMMPX = MMPX +UpscaleBicubic = ภาพอัพสเกลแบบไบคิวบิค UpscaleSpline36 = ภาพลดความละเอียด เน้นสเกลคมขึ้น VideoSmoothingAA = ภาพในฉากคัตซีนดูเนียนยิ่งขึ้น Vignette = ภาพเน้นขอบหม่น @@ -1104,12 +1113,14 @@ Uninstall = ถอนการติดตั้ง (none detected) = (ไม่มีการตรวจพบ) 3D API = แอพพลิเคชั่น โปรแกรม อินเตอร์เฟซ สามมิติ ABI = แอพพลิเคชั่น ไบนารี่ อินเตอร์เฟซ +Achievement tests = ทดสอบเป้าหมายความสำเร็จ API Version = เวอร์ชั่นของแอพพลิเคชั่น โปรแกรม อินเตอร์เฟซ Audio Information = ข้อมูลของระบบเสียง Board = ชื่อบอร์ด Build Config = การกำหนดค่า Build Configuration = การกำหนดค่าที่ถูกสร้างไว้ Built by = สร้างโดย +Clear = ล้างแคช Compressed texture formats = รูปแบบการบีบอัดเท็คเจอร์ Core Context = บริบทของแกน Cores = จำนวนแกน @@ -1117,6 +1128,7 @@ CPU Extensions = ส่วนขยายซีพียู CPU Information = ข้อมูลของหน่วยประมวลผลหลัก CPU Name = ชื่อหน่วยประมวลผลหลัก D3DCompiler Version = เวอร์ชั่นตัวคอมไพล์ของ D3D +Data size = ขนาดข้อมูล Debug = แก้ไขบั๊ก Debugger Present = ตัวช่วยแก้ไขบั๊ก Depth buffer format = รูปแบบบัฟเฟอร์ความลึก @@ -1128,16 +1140,26 @@ DPI = DPI Driver bugs = ไดรเวอร์บั๊ก Driver Version = เวอร์ชั่นของไดรเวอร์ EGL Extensions = ส่วนขยายอีจีแอล +Error = เอ๋อ +Failure = ล้มเหลว Frames per buffer = จำนวนเฟรมต่อบัฟเฟอร์ GPU Flags = แฟล็กของหน่วยประมวลผลกราฟิก GPU Information = ข้อมูลของหน่วยประมวลผลกราฟิก High precision float range = ขอบเขตจำนวนทศนิยมช่วงค่าความแม่นยำสูง High precision int range = ขอบเขตจำนวนเต็มช่วงค่าความแม่นยำสูง +Icon cache = แคชไอคอน +Image data count = จำนวนข้อมูลรูปภาพ +Info = ข้อมูล +Internals = ข้อมูลภายใน JIT available = JIT ใช้งานได้ Lang/Region = ภาษา/ภูมิภาค +Leaderboard tracker: Hide = การติดตามกระดานแต้มผู้นำ: ซ่อน +Leaderboard tracker: Show = การติดตามกระดานแต้มผู้นำ: แสดง +Leaderboard tracker: Update = การติดตามกระดานแต้มผู้นำ: อัพเดท Memory Page Size = ขนาดหน้าของหน่วยความจำ Native Resolution = ความละเอียดดั้งเดิม No GPU driver bugs detected = ไม่พบไดรเวอร์บั๊กใน GPU รุ่นนี้ +Notification tests = ทดสอบการแจ้งเตือน OGL Extensions = ส่วนขยายโอจีแอล OpenGL ES 2.0 Extensions = ส่วนขยายของโอเพ่นจีแอล อีเอส 2.0 OpenGL ES 3.0 Extensions = ส่วนขยายของโอเพ่นจีแอล อีเอส 3.0 @@ -1146,23 +1168,27 @@ Optimal frames per buffer = จำนวนเฟรมต่อบัฟเฟ Optimal sample rate = อัตราค่าความถี่เสียงที่เหมาะสม OS Information = ข้อมูลของระบบปฏิบัติการ PPSSPP build = รูปแบบ PPSSPP +Progress tests = ทดสอบความคืบหน้า Refresh rate = อัตราการรีเฟรชของหน้าจอ Release = ใช้งาน RW/RX exclusive = RW/RX แบบพิเศษ Sample rate = อัตราค่าความถี่เสียง Shading Language = ภาษาของเฉดเดอร์ Storage = พื้นที่เก็บข้อมูล +Success = สำเร็จ Sustained perf mode = โหมดประสิทธิภาพที่ยั่งยืน System Information = ข้อมูลโดยรวมของระบบ System Name = ชื่อระบบ System Version = เวอร์ชั่นของระบบ +Texture count = จำนวนเท็คเจอร์ Threads = จำนวนเธรด +UI Resolution = ความละเอียดของอินเตอร์เฟซ Vendor = ชื่อหน่วยประมวลผลกราฟิก Vendor (detected) = ผู้จัดจำหน่าย (ที่ตรวจพบ) Version Information = ข้อมูลของเวอร์ชั่น Vulkan Extensions = ส่วนขยายของวัลแคน Vulkan Features = ฟีเจอร์วัลแคน -UI Resolution = ความละเอียดของอินเตอร์เฟซ +Warning = คำเตือน [System] (broken) = (เสียหาย) @@ -1188,7 +1214,7 @@ Date Format = รูปแบบวันที่ Day Light Saving = เวลาออมแสง DDMMYYYY = วัน/เดือน/ปี Decrease size = ลดขนาดไอคอน -Developer Tools = เครื่องมือสำหรับนักพัฒนา +Developer Tools = เครื่องมือผู้พัฒนา Display Extra Info = แสดงข้อมูลเพิ่มเติม Display Games on a grid = แสดงแบบกริดในแถบ "เกมอื่นๆ" Display Homebrew on a grid = แสดงแบบกริดในแถบ "เกมโฮมบริว" @@ -1240,6 +1266,7 @@ Record Audio = อัดบันทึกเสียง Record Display = อัดบันทึกวีดีโอ Reset Recording on Save/Load State = เริ่มการอัดบันทึกไฟล์ใหม่ เมื่อกดเซฟ/โหลดสเตทเกม Restore Default Settings = รีเซ็ตการตั้งค่าของ PPSSPP ทั้งหมด +RetroAchievements = RetroAchievements Rewind Snapshot Interval = เซฟสเตทพื้นหลังแบบอัตโนมัติ (สูบแรม) Save path in installed.txt = บันทึกข้อมูลตามเส้นทางที่เลือกไว้ Save path in My Documents = บันทึกข้อมูลไว้ที่ My Documents @@ -1309,7 +1336,9 @@ New version of PPSSPP available = PPSSPP เวอร์ชั่นใหม่ [VR] % of native FoV = % ของค่า FoV ดั้งเดิม 6DoF movement = การเคลื่อนไหวแบบ 6DoF +Camera type = ปรับแต่งประเภทมุมกล้อง Distance to 2D menus and scenes = ระยะห่างจากเมนูสองมิติ และฉาก +Distance to 3D scenes when VR disabled = ระยะห่างจากฉากสามมิติ เมื่อ VR ถูกปิดใช้งาน Experts only = สำหรับผู้เชี่ยวชาญเท่านั้น Field of view scale = สเกลของมุมมองพื้นที่รอบตัว Force 72Hz update = บังคับรีเฟรชเรทไปที่ 72Hz @@ -1320,7 +1349,6 @@ Heads-up display scale = สเกลขนาดของ HUD Manual switching between flat screen and VR using SCREEN key = สลับโหมดระหว่าง จอแบน และ VR โดยใช้ปุ่ม SCREEN Map controller movements to keys = เซ็ตปุ่มควบคุมด้วยท่าทางผ่านจอย Map HMD rotations on keys instead of VR camera = เซ็ตการควบคุมการหมุน HMD โดยใช้ปุ่มกด แทนการใช้กล้อง VR -Camera type = ปรับแต่งประเภทมุมกล้อง Motion needed to generate action = จำเป็นต้องขยับท่าทางเพื่อเซ็ตปุ่มที่ต้องการ Stereoscopic vision (Experimental) = มุมมองภาพแบบสามมิติ (ขั้นทดสอบ) Virtual reality = ความเป็นจริงเสมือน From a181f6d5b8463d1aff77703144805c7197803d21 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 27 Jul 2023 22:16:29 -0700 Subject: [PATCH 116/167] riscv: Add a comment for FMUL testing later. --- Core/MIPS/RiscV/RiscVCompFPU.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index 27ab1a943f..84c7faa832 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -50,6 +50,8 @@ void RiscVJit::CompIR_FArith(IRInst inst) { case IROp::FMul: fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + // TODO: If FMUL consistently produces NAN across chip vendors, we can skip this. + // Luckily this does match the RISC-V canonical NAN. if (inst.src1 != inst.src2) { // These will output 0x80/0x01 if infinity, 0x10/0x80 if zero. // We need to check if one is infinity and the other zero. From c19aa05b60d3fb395fc950f93205e3bd223dcd9d Mon Sep 17 00:00:00 2001 From: Eric Warmenhoven Date: Fri, 28 Jul 2023 03:37:07 -0400 Subject: [PATCH 117/167] Fix libretro build on apple platforms --- libretro/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libretro/CMakeLists.txt b/libretro/CMakeLists.txt index da76ff3e02..7ebc7a6e18 100644 --- a/libretro/CMakeLists.txt +++ b/libretro/CMakeLists.txt @@ -23,7 +23,7 @@ endif() if(NOT MSVC) if (APPLE OR IOS) - target_link_libraries(ppsspp_libretro "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/libretro.osx.def") + target_link_libraries(ppsspp_libretro objc "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/libretro.osx.def") else() target_link_libraries(ppsspp_libretro "-Wl,-Bsymbolic") endif() From f88d1a287eb931039b58fafd3714623d6c6141dd Mon Sep 17 00:00:00 2001 From: Hoe Hao Cheng Date: Tue, 25 Jul 2023 21:57:39 +0800 Subject: [PATCH 118/167] SDL: implement font fallback for TextDrawerSDL --- CMakeLists.txt | 6 + Common/Render/Text/draw_text_sdl.cpp | 264 +++++++++++++++++++++++++-- Common/Render/Text/draw_text_sdl.h | 14 ++ 3 files changed, 264 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f34361dd52..34d8f63b84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -259,6 +259,7 @@ endif() if(NOT LIBRETRO AND NOT IOS AND NOT MACOSX) find_package(SDL2) find_package(SDL2_TTF) + find_package(Fontconfig) endif() if(MACOSX AND NOT IOS) @@ -1322,6 +1323,11 @@ else() if(SDL2_TTF_FOUND) add_definitions(-DUSE_SDL2_TTF) set(nativeExtraLibs ${nativeExtraLibs} SDL2_ttf::SDL2_ttf) + + if(FONTCONFIG_FOUND) + add_definitions(-DUSE_SDL2_TTF_FONTCONFIG) + set(nativeExtraLibs ${nativeExtraLibs} Fontconfig::Fontconfig) + endif() endif() if(APPLE) set(nativeExtra ${nativeExtra} diff --git a/Common/Render/Text/draw_text_sdl.cpp b/Common/Render/Text/draw_text_sdl.cpp index 9e228ebef7..6f50891d19 100644 --- a/Common/Render/Text/draw_text_sdl.cpp +++ b/Common/Render/Text/draw_text_sdl.cpp @@ -1,15 +1,15 @@ #include "ppsspp_config.h" -#include -#include - #include "Common/Log.h" +#include "Common/StringUtils.h" #include "Common/System/Display.h" #include "Common/GPU/thin3d.h" #include "Common/Data/Hash/Hash.h" #include "Common/Data/Text/WrapText.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/File/VFS/VFS.h" +#include "Common/File/FileUtil.h" +#include "Common/File/Path.h" #include "Common/Render/Text/draw_text.h" #include "Common/Render/Text/draw_text_sdl.h" @@ -18,19 +18,193 @@ #include "SDL2/SDL.h" #include "SDL2/SDL_ttf.h" -const uint32_t MAX_WIDTH = 4096; - TextDrawerSDL::TextDrawerSDL(Draw::DrawContext *draw): TextDrawer(draw) { if (TTF_Init() < 0) { ERROR_LOG(G3D, "Unable to initialize SDL2_ttf"); } dpiScale_ = CalculateDPIScale(); + +#if defined(USE_SDL2_TTF_FONTCONFIG) + config = FcInitLoadConfigAndFonts(); +#endif + + PrepareFallbackFonts(); } TextDrawerSDL::~TextDrawerSDL() { ClearCache(); TTF_Quit(); + +#if defined(USE_SDL2_TTF_FONTCONFIG) + FcConfigDestroy(config); + FcFini(); +#endif +} + +// If a user complains about missing characters on SDL, re-visit this! +void TextDrawerSDL::PrepareFallbackFonts() { +#if defined(USE_SDL2_TTF_FONTCONFIG) + FcObjectSet *os = FcObjectSetBuild (FC_FILE, FC_INDEX, (char *) 0); + + FcPattern *names[] = { + FcNameParse((const FcChar8 *) "Source Han Sans Medium"), + FcNameParse((const FcChar8 *) "Droid Sans Bold"), + FcNameParse((const FcChar8 *) "DejaVu Sans Condensed"), + FcNameParse((const FcChar8 *) "Noto Sans CJK Medium"), + FcNameParse((const FcChar8 *) "Noto Sans Hebrew Medium"), + FcNameParse((const FcChar8 *) "Noto Sans Lao Medium"), + FcNameParse((const FcChar8 *) "Noto Sans Thai Medium") + }; + + for (int i = 0; i < ARRAY_SIZE(names); i++) { + FcFontSet *foundFonts = FcFontList(config, names[i], os); + + for (int j = 0; foundFonts && j < foundFonts->nfont; ++j) { + FcPattern* font = foundFonts->fonts[j]; + FcChar8 *path; + int fontIndex; + + if (FcPatternGetInteger(font, FC_INDEX, 0, &fontIndex) != FcResultMatch) { + fontIndex = 0; // The 0th face is guaranteed to exist + } + + if (FcPatternGetString(font, FC_FILE, 0, &path) == FcResultMatch) { + std::string path_str((const char*)path); + fallbackFontPaths_.push_back(std::make_pair(path_str, fontIndex)); + } + } + + if (foundFonts) { + FcFontSetDestroy(foundFonts); + } + + FcPatternDestroy(names[i]); + } + + if (os) { + FcObjectSetDestroy(os); + } +#elif PPSSPP_PLATFORM(MAC) + const char *fontDirs[] = { + "/System/Library/Fonts/", + "/System/Library/Fonts/Supplemental/", + "/Library/Fonts/" + }; + + const char *fallbackFonts[] = { + "Hiragino Sans GB.ttc", + "PingFang.ttc", + "PingFang SC.ttc", + "PingFang TC.ttc", + "ヒラギノ角ゴシック W4.ttc", + "AppleGothic.ttf", + "Arial Unicode.ttf", + }; + + for (int i = 0; i < ARRAY_SIZE(fontDirs); i++) { + for (int j = 0; j < ARRAY_SIZE(fallbackFonts); j++) { + Path fontPath = Path(fontDirs[i]) / fallbackFonts[j]; + + if (File::Exists(fontPath)) { + TTF_Font *openedFont = TTF_OpenFont(fontPath.ToString().c_str(), 24); + int64_t numFaces = TTF_FontFaces(openedFont); + + for (int k = 0; k < numFaces; k++) { + TTF_Font *fontFace = TTF_OpenFontIndex(fontPath.ToString().c_str(), 24, k); + std::string fontFaceName(TTF_FontFaceStyleName(fontFace)); + TTF_CloseFont(fontFace); + + if (strstr(fontFaceName.c_str(), "Medium") || + strstr(fontFaceName.c_str(), "Regular")) + { + fallbackFontPaths_.push_back(std::make_pair(fontPath.ToString(), k)); + break; + } + } + + TTF_CloseFont(openedFont); + } + } + } +#else + // We don't have a fallback font for this platform. + // Unsupported characters will be rendered as squares. +#endif +} + +// Count leading set bits. For use in UTF-8 decoding. +int LeadingOnes(uint8_t byte) { + for (int i = 7; i >= 0; i--) { + if (!((byte >> i) & 1)) return 7 - i; + } + + return 8; +} + +uint32_t TextDrawerSDL::CheckMissingGlyph(std::string& text) { + TTF_Font *font = fontMap_.find(fontHash_)->second; + + uint32_t missingGlyph = 0; + const int length = text.length(); + for (int i = 0; i < length; i++) { + uint32_t glyph; // UTF-32 char + + // Decoding UTF-8 string into UTF-32 codepoints + if (LeadingOnes(text[i]) == 2 && (i + 1 < length)) { + glyph = text[i] & 0b00011111; + glyph = (glyph << 6) | (text[++i] & 0b00111111); + } else if (LeadingOnes(text[i]) == 3 && (i + 2 < length)) { + glyph = text[i] & 0b00001111; + glyph = (glyph << 6) | (text[++i] & 0b00111111); + glyph = (glyph << 6) | (text[++i] & 0b00111111); + } else if (LeadingOnes(text[i]) == 4 && (i + 3 < length)) { + glyph = text[i] & 0b00000111; + glyph = (glyph << 6) | (text[++i] & 0b00111111); + glyph = (glyph << 6) | (text[++i] & 0b00111111); + glyph = (glyph << 6) | (text[++i] & 0b00111111); + } else { + glyph = text[i]; + } + + if (!TTF_GlyphIsProvided32(font, glyph)) { + missingGlyph = glyph; + break; + } + } + + return missingGlyph; +} + +// If this returns true, the first font in fallbackFonts_ can be used as a fallback. +bool TextDrawerSDL::FindFallbackFonts(uint32_t missingGlyph, int ptSize) { + // If we encounter a missing glyph, try to use one of the fallback fonts. + for (int i = 0; i < fallbackFonts_.size(); i++) { + TTF_Font *fallbackFont = fallbackFonts_[i]; + if (TTF_GlyphIsProvided32(fallbackFont, missingGlyph)) { + fallbackFonts_.erase(fallbackFonts_.begin() + i); + fallbackFonts_.insert(fallbackFonts_.begin(), fallbackFont); + return true; + } + } + + // If none of the loaded fonts can handle it, load more fonts. + for (int i = 0; i < fallbackFontPaths_.size(); i++) { + std::string& fontPath = fallbackFontPaths_[i].first; + int faceIndex = fallbackFontPaths_[i].second; + + TTF_Font *font = TTF_OpenFontIndex(fontPath.c_str(), ptSize, faceIndex); + + if (TTF_GlyphIsProvided32(font, missingGlyph)) { + fallbackFonts_.insert(fallbackFonts_.begin(), font); + fallbackFontPaths_.erase(fallbackFontPaths_.begin() + i); + return true; + } else { + TTF_CloseFont(font); + } + } + + return false; } uint32_t TextDrawerSDL::SetFont(const char *fontName, int size, int flags) { @@ -47,7 +221,7 @@ uint32_t TextDrawerSDL::SetFont(const char *fontName, int size, int flags) { const char *useFont = fontName ? fontName : "Roboto-Condensed.ttf"; const int ptSize = (int)((size + 6) / dpiScale_); - TTF_Font *font = TTF_OpenFont(useFont, ptSize); + TTF_Font *font = TTF_OpenFont(useFont, ptSize); if (!font) { File::FileInfo fileInfo; @@ -79,10 +253,17 @@ void TextDrawerSDL::MeasureString(const char *str, size_t len, float *w, float * entry = iter->second.get(); } else { TTF_Font *font = fontMap_.find(fontHash_)->second; + int ptSize = TTF_FontHeight(font) / 1.35; + + uint32_t missingGlyph = CheckMissingGlyph(key.text); + + if (missingGlyph && FindFallbackFonts(missingGlyph, ptSize)) { + font = fallbackFonts_[0]; + } int width = 0; int height = 0; - TTF_SizeUTF8(font, str, &width, &height); + TTF_SizeUTF8(font, key.text.c_str(), &width, &height); entry = new TextMeasureEntry(); entry->width = width; @@ -103,14 +284,45 @@ void TextDrawerSDL::MeasureStringRect(const char *str, size_t len, const Bounds WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w, wrap); } - TTF_Font* font = fontMap_.find(fontHash_)->second; + TTF_Font *font = fontMap_.find(fontHash_)->second; + int ptSize = TTF_FontHeight(font) / 1.35; + uint32_t missingGlyph = CheckMissingGlyph(toMeasure); - int width = 0; - int height = 0; - TTF_SizeUTF8(font, str, &width, &height); + if (missingGlyph && FindFallbackFonts(missingGlyph, ptSize)) { + font = fallbackFonts_[0]; + } - *w = width * fontScaleX_ * dpiScale_; - *h = height * fontScaleY_ * dpiScale_; + std::vector lines; + SplitString(toMeasure, '\n', lines); + + int total_w = 0; + int total_h = 0; + for (size_t i = 0; i < lines.size(); i++) { + CacheKey key{ lines[i], fontHash_ }; + + TextMeasureEntry *entry; + auto iter = sizeCache_.find(key); + if (iter != sizeCache_.end()) { + entry = iter->second.get(); + } else { + int width = 0; + int height = 0; + TTF_SizeUTF8(font, lines[i].c_str(), &width, &height); + entry = new TextMeasureEntry(); + entry->width = width; + entry->height = height; + sizeCache_[key] = std::unique_ptr(entry); + } + + entry->lastUsedFrame = frameCount_; + if (total_w < entry->width) { + total_w = entry->width; + } + total_h += entry->height; + } + + *w = total_w * fontScaleX_ * dpiScale_; + *h = total_h * fontScaleY_ * dpiScale_; } void TextDrawerSDL::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { @@ -160,16 +372,24 @@ void TextDrawerSDL::DrawString(DrawBuffer &target, const char *str, float x, flo target.Flush(true); } } + void TextDrawerSDL::DrawStringBitmap(std::vector &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align) { if (!strlen(str)) { bitmapData.clear(); return; } - std::string processed_str(str); - processed_str = std::regex_replace(processed_str, std::regex("&&"), "&"); + // Replace "&&" with "&" + std::string processedStr = ReplaceAll(str, "&&", "&"); TTF_Font *font = fontMap_.find(fontHash_)->second; + int ptSize = TTF_FontHeight(font) / 1.35; + + uint32_t missingGlyph = CheckMissingGlyph(processedStr); + + if (missingGlyph && FindFallbackFonts(missingGlyph, ptSize)) { + font = fallbackFonts_[0]; + } if (align & ALIGN_HCENTER) TTF_SetFontWrappedAlign(font, TTF_WRAPPED_ALIGN_CENTER); @@ -179,7 +399,7 @@ void TextDrawerSDL::DrawStringBitmap(std::vector &bitmapData, TextStrin TTF_SetFontWrappedAlign(font, TTF_WRAPPED_ALIGN_RIGHT); SDL_Color fgColor = { 0xFF, 0xFF, 0xFF, 0xFF }; - SDL_Surface *text = TTF_RenderUTF8_Blended(font, processed_str.c_str(), fgColor); + SDL_Surface *text = TTF_RenderUTF8_Blended_Wrapped(font, processedStr.c_str(), fgColor, 0); SDL_LockSurface(text); entry.texture = nullptr; @@ -187,7 +407,7 @@ void TextDrawerSDL::DrawStringBitmap(std::vector &bitmapData, TextStrin entry.bmHeight = entry.height = text->h; entry.lastUsedFrame = frameCount_; - uint32_t *imageData = (uint32_t *) text->pixels; + uint32_t *imageData = (uint32_t *)text->pixels; if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) { bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t)); @@ -196,15 +416,15 @@ void TextDrawerSDL::DrawStringBitmap(std::vector &bitmapData, TextStrin for (int x = 0; x < entry.bmWidth; x++) { for (int y = 0; y < entry.bmHeight; y++) { uint64_t index = entry.bmWidth * y + x; - bitmapData16[entry.bmWidth * y + x] = 0xfff0 | (imageData[index] >> 28); + bitmapData16[index] = 0xfff0 | (imageData[index] >> 28); } } } else if (texFormat == Draw::DataFormat::R8_UNORM) { bitmapData.resize(entry.bmWidth * entry.bmHeight); for (int x = 0; x < entry.bmWidth; x++) { for (int y = 0; y < entry.bmHeight; y++) { - uint64_t index = text->pitch / sizeof(uint32_t) * y + x; - bitmapData[entry.bmWidth * y + x] = imageData[index] >> 24; + uint64_t index = entry.bmWidth * y + x; + bitmapData[index] = imageData[index] >> 24; } } } else { @@ -256,7 +476,11 @@ void TextDrawerSDL::ClearCache() { for (auto iter : fontMap_) { TTF_CloseFont(iter.second); } + for (auto iter : fallbackFonts_) { + TTF_CloseFont(iter); + } fontMap_.clear(); + fallbackFonts_.clear(); fontHash_ = 0; } diff --git a/Common/Render/Text/draw_text_sdl.h b/Common/Render/Text/draw_text_sdl.h index b86c96acb6..7c047ee48c 100644 --- a/Common/Render/Text/draw_text_sdl.h +++ b/Common/Render/Text/draw_text_sdl.h @@ -5,6 +5,10 @@ #include #include "Common/Render/Text/draw_text.h" +#if defined(USE_SDL2_TTF_FONTCONFIG) +#include +#endif + // SDL2_ttf's TTF_Font is a typedef of _TTF_Font. struct _TTF_Font; @@ -24,10 +28,20 @@ public: protected: void ClearCache() override; + void PrepareFallbackFonts(); + uint32_t CheckMissingGlyph(std::string& text); + bool FindFallbackFonts(uint32_t missingGlyph, int ptSize); uint32_t fontHash_; std::map fontMap_; std::map> cache_; std::map> sizeCache_; + + std::vector<_TTF_Font *> fallbackFonts_; + std::vector> fallbackFontPaths_; // path and font face index + +#if defined(USE_SDL2_TTF_FONTCONFIG) + FcConfig *config; +#endif }; From 1c890be702ed3157e678f0e8b820c35bacf5904f Mon Sep 17 00:00:00 2001 From: Hoe Hao Cheng Date: Fri, 28 Jul 2023 21:00:09 +0800 Subject: [PATCH 119/167] Use common UTF8 infrastructure instead of rewriting one --- Common/Render/Text/draw_text_sdl.cpp | 33 ++++------------------------ 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/Common/Render/Text/draw_text_sdl.cpp b/Common/Render/Text/draw_text_sdl.cpp index 6f50891d19..cdca83d170 100644 --- a/Common/Render/Text/draw_text_sdl.cpp +++ b/Common/Render/Text/draw_text_sdl.cpp @@ -133,39 +133,14 @@ void TextDrawerSDL::PrepareFallbackFonts() { #endif } -// Count leading set bits. For use in UTF-8 decoding. -int LeadingOnes(uint8_t byte) { - for (int i = 7; i >= 0; i--) { - if (!((byte >> i) & 1)) return 7 - i; - } - - return 8; -} - uint32_t TextDrawerSDL::CheckMissingGlyph(std::string& text) { TTF_Font *font = fontMap_.find(fontHash_)->second; + UTF8 utf8Decoded(text.c_str()); uint32_t missingGlyph = 0; - const int length = text.length(); - for (int i = 0; i < length; i++) { - uint32_t glyph; // UTF-32 char - - // Decoding UTF-8 string into UTF-32 codepoints - if (LeadingOnes(text[i]) == 2 && (i + 1 < length)) { - glyph = text[i] & 0b00011111; - glyph = (glyph << 6) | (text[++i] & 0b00111111); - } else if (LeadingOnes(text[i]) == 3 && (i + 2 < length)) { - glyph = text[i] & 0b00001111; - glyph = (glyph << 6) | (text[++i] & 0b00111111); - glyph = (glyph << 6) | (text[++i] & 0b00111111); - } else if (LeadingOnes(text[i]) == 4 && (i + 3 < length)) { - glyph = text[i] & 0b00000111; - glyph = (glyph << 6) | (text[++i] & 0b00111111); - glyph = (glyph << 6) | (text[++i] & 0b00111111); - glyph = (glyph << 6) | (text[++i] & 0b00111111); - } else { - glyph = text[i]; - } + for (int i = 0; i < text.length(); ) { + uint32_t glyph = utf8Decoded.next(); + i = utf8Decoded.byteIndex(); if (!TTF_GlyphIsProvided32(font, glyph)) { missingGlyph = glyph; From aaa7e9017486eda4f867d642d99a5c77aec007dc Mon Sep 17 00:00:00 2001 From: Hoe Hao Cheng Date: Fri, 28 Jul 2023 21:33:19 +0800 Subject: [PATCH 120/167] SDL: fix a curious crash --- Common/Render/Text/draw_text_sdl.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Common/Render/Text/draw_text_sdl.cpp b/Common/Render/Text/draw_text_sdl.cpp index cdca83d170..0de2b74397 100644 --- a/Common/Render/Text/draw_text_sdl.cpp +++ b/Common/Render/Text/draw_text_sdl.cpp @@ -357,6 +357,20 @@ void TextDrawerSDL::DrawStringBitmap(std::vector &bitmapData, TextStrin // Replace "&&" with "&" std::string processedStr = ReplaceAll(str, "&&", "&"); + // If a string includes only newlines, SDL2_ttf will refuse to render it + // thinking it is empty. Add a space to avoid that. + bool isAllNewline = true; + for (int i = 0; i < processedStr.size(); i++) { + if (processedStr[i] != '\n') { + isAllNewline = false; + break; + } + } + + if (isAllNewline) { + processedStr.push_back(' '); + } + TTF_Font *font = fontMap_.find(fontHash_)->second; int ptSize = TTF_FontHeight(font) / 1.35; From c2e6ec7f7e4c495722eac75072426b8017253346 Mon Sep 17 00:00:00 2001 From: Jaakko Saarikko <74954275+styxnix@users.noreply.github.com> Date: Sat, 29 Jul 2023 17:15:50 +0300 Subject: [PATCH 121/167] Update fi_FI.ini Translation work on Finnish language. --- assets/lang/fi_FI.ini | 756 +++++++++++++++++++++--------------------- 1 file changed, 378 insertions(+), 378 deletions(-) diff --git a/assets/lang/fi_FI.ini b/assets/lang/fi_FI.ini index c05d06318f..192e7d4825 100644 --- a/assets/lang/fi_FI.ini +++ b/assets/lang/fi_FI.ini @@ -158,41 +158,41 @@ Refresh Rate = Ruudunpäivitysnopeus # If your language does not show well with the default font, you can use Font to specify a different one. # Just add it to your language's ini file and uncomment it (remove the # by Font). #Font = Trebuchet MS -About PPSSPP... = &About PPSSPP... -Auto = &Auto -Auto Max Quality = Auto Max &Quality -Backend = Rendering &Backend (Restarts PPSSPP) +About PPSSPP... = &Tietoa PPSSPP:stä... +Auto = &Automaattinen +Auto Max Quality = Automaattinen maksimi &laatu +Backend = Renderöinnin taustaraja&pinta (PPSSPP uudelleenkäynnistyy) Bicubic = &Bicubic Break = Break Break on Load = Break on load -Buy Gold = Buy &Gold -Control Mapping... = C&ontrol Mapping... -Copy PSP memory base address = Copy PSP memory base &address -Debugging = &Debug -Deposterize = &Deposterize +Buy Gold = Osta &Gold +Control Mapping... = &Ohjauksen kartoitus... +Copy PSP memory base address = Kopioi PSP:n muistin &perusosoite +Debugging = &Vianetsintä +Deposterize = &Häiriöiden korjaus Direct3D9 = &Direct3D9 Direct3D11 = Direct3D &11 Disassembly = &Disassembly... Discord = Discord -Display Layout && Effects = Display layout && effects... -Display Rotation = Display rotation -Dump Next Frame to Log = D&ump Next Frame to Log -Emulation = &Emulation -Enable Cheats = Enable &Cheats -Enable Sound = Enable S&ound -Exit = E&xit -Extract File... = E&xtract File... -File = &File -Frame Skipping = &Frame skipping -Frame Skipping Type = Frame skipping type -Fullscreen = Fu&llscreen -Game Settings = &Game Settings +Display Layout && Effects = Näyttöasettelu ja -&efektit +Display Rotation = Näytön kierto +Dump Next Frame to Log = Tallenna seuraava &kuva +Emulation = &Emulaatio +Enable Cheats = Ota &huijaukset käyttöön +Enable Sound = Ota &ääni käyttöön +Exit = &Poistu +Extract File... = &Pura tiedosto... +File = &Tiedosto +Frame Skipping = &Kuvan ohitus +Frame Skipping Type = Kuvan ohituksen tyyppi +Fullscreen = &Koko näyttö +Game Settings = &Pelin asetukset GE Debugger... = GE Debugge&r... GitHub = Git&Hub Hardware Transform = &Hardware Transform -Help = &Help -Hybrid = &Hybrid -Hybrid + Bicubic = H&ybrid + Bicubic +Help = &Apua +Hybrid = &Hybridi +Hybrid + Bicubic = H&ybridi + Bicubic Ignore Illegal Reads/Writes = &Ignore Illegal Reads/Writes Ignore Windows Key = Ignore Windows Key Keep PPSSPP On Top = &Keep PPSSPP on Top @@ -222,37 +222,37 @@ Pause = &Pause Pause When Not Focused = &Pause When Not Focused Portrait = Portrait Portrait reversed = Portrait reversed -PPSSPP Forums = PPSSPP &Forums -Record = &Record -Record Audio = Record &audio -Record Display = Record &display -Rendering Mode = Rendering M&ode -Rendering Resolution = &Rendering Resolution -Reset = R&eset -Reset Symbol Table = Reset S&ymbol Table -Run = &Run -Save .sym File... = Sav&e .sym File... -Save Map File... = &Save Map File... -Save State = S&ave State -Save State File... = &Save State File... -Savestate Slot = Saves&tate Slot -Screen Scaling Filter = Scr&een Scaling Filter -Show Debug Statistics = Show Debu&g Statistics -Show FPS Counter = Show &FPS Counter -Skip Number of Frames = Skip number of frames -Skip Percent of FPS = Skip percent of FPS -Stop = &Stop -Switch UMD = Switch UMD -Take Screenshot = &Take Screenshot -Texture Filtering = Te&xture Filtering -Texture Scaling = &Texture Scaling -Use Lossless Video Codec (FFV1) = &Use lossless video codec (FFV1) -Use output buffer for video = Use output buffer for video -Vertex Cache = &Vertex Cache +PPSSPP Forums = PPSSPP:n &foorumit +Record = &Nauhoita +Record Audio = Nauhoita &ääntä +Record Display = Nauhoita &näyttöä +Rendering Mode = Ren&deröinti tapa +Rendering Resolution = &Renderöinti resoluutio +Reset = &Nollaa +Reset Symbol Table = Nollaa s&ymbolitaulukko +Run = &Suorita +Save .sym File... = Tallenna .&sym-tiedosto... +Save Map File... = Tallenna &kartoitustiedosto... +Save State = Tallenna &tila +Save State File... = Tilatallennuksen ti&edosto... +Savestate Slot = Tilatallennuksen &lohko +Screen Scaling Filter = N&äytön skaalaussuodatin +Show Debug Statistics = Näytä &vianetsinnän tiedot +Show FPS Counter = Näytä kuvalaskuri (FPS) +Skip Number of Frames = Ohita tämä määrä kuvia +Skip Percent of FPS = Ohita prosenttiosuus kuvista +Stop = &Pysäytä +Switch UMD = Vaihda UMD +Take Screenshot = Ota &kuvakaappaus +Texture Filtering = &Tekstuurien suodatus +Texture Scaling = &Tekstuurien skaalaus +Use Lossless Video Codec (FFV1) = Käytä häviötöntä &videokoodekkia (FFV1) +Use output buffer for video = Käytä ulostulopuskuria videolle +Vertex Cache = &Vertex-välimuisti VSync = VS&ync Vulkan = Vulkan -Window Size = &Window Size -www.ppsspp.org = Visit www.&ppsspp.org +Window Size = &Ikkunan koko +www.ppsspp.org = Käy osoitteessa www.&ppsspp.org xBRZ = &xBRZ [Developer] @@ -263,9 +263,9 @@ Block address = Block address By Address = By address Copy savestates to memstick root = Copy save states to Memory Stick root Create/Open textures.ini file for current game = Create/Open textures.ini file for current game -Current = Current -Dev Tools = Development tools -DevMenu = DevMenu +Current = Nykyinen +Dev Tools = Kehitystökalut +DevMenu = Kehitysvalikko Disabled JIT functionality = Disabled JIT functionality Draw Frametimes Graph = Draw frametimes graph Dump Decrypted Eboot = Dump decrypted EBOOT.BIN on game boot @@ -328,7 +328,7 @@ ConnectingPleaseWait = Connecting.\nPlease wait... ConnectionName = Connection name Corrupted Data = Corrupted data Delete = Poista -Delete all = Delete all +Delete all = Poista kaikki Delete completed = Poisto onnistui. DeleteConfirm = Tämä tallennus poistetaan.\nHaluatko varmasti jatkaa? DeleteConfirmAll = Do you really want to delete all\nyour save data for this game? @@ -378,9 +378,9 @@ Retry = Retry Save = Save Save completed = Tallennus onnistui. Saving = Tallentaa\nOdota... -SavingFailed = Unable to save data. +SavingFailed = Ei voitu tallentaa. Search = Search -seconds, 0:off = seconds, 0 = off +seconds, 0:off = sekunteissa, 0 = pois Select = Valitse Settings = Settings Shift = Shift @@ -394,7 +394,7 @@ There is no data = Dataa ei ole. Toggle All = Toggle all Toggle List = Toggle list Unsupported = Ei tuettu -Username = Username +Username = Käyttäjänimi When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP When you save, it will not work on outdated PSP Firmware anymore = When you save, it will not work on outdated PSP firmware anymore Yes = Kyllä @@ -474,7 +474,7 @@ Hong Kong = Hong Kong InstallData = Data install Japan = Japani Korea = Korea -MB = MB +MB = Mt One moment please... = Pieni hetki... Play = Pelaa Remove From Recent = Poista "äskettäin" listalta... @@ -487,9 +487,9 @@ Use UI background = Use UI background [Graphics] % of the void = % of the void % of viewport = % of viewport -%, 0:unlimited = %, 0 = unlimited -(supersampling) = (supersampling) -(upscaling) = (upscaling) +%, 0:unlimited = %, 0 = rajoittamaton +(supersampling) = (ylinäytteistys) +(upscaling) = (ylösskaalaus) 1x PSP = 1x PSP 2x = 2x 2x PSP = 2x PSP @@ -508,15 +508,15 @@ Use UI background = Use UI background 16x = 16x Aggressive = Aggressiivinen Alternative Speed = Vaihtoehtoinen nopeus -Alternative Speed 2 = Vaihtoehtoinen nopeus 2 (prosenteissa, 0 = rajoittamaton) +Alternative Speed 2 = Vaihtoehtoinen nopeus 2 Anisotropic Filtering = Anisotrooppisen suodatus -Antialiasing (MSAA) = Antialiasing (MSAA) +Antialiasing (MSAA) = Pehmentäminen (MSAA) Aspect Ratio = Kuvasuhde Auto = Automaattinen -Auto (1:1) = Auto (1:1) -Auto (same as Rendering) = Automaattinen (sama kuin renderöinti resoluutio) -Auto FrameSkip = Automaattinen ruudunohitus -Auto Max Quality = Auto Max Quality +Auto (1:1) = Automaattinen (1:1) +Auto (same as Rendering) = Automaattinen (sama kuin renderöintiresoluutio) +Auto FrameSkip = Automaattinen kuvanohitus +Auto Max Quality = Automaattinen maksimi laatu Auto Scaling = Automaattinen skaalaus Backend = Taustarajapinta Balanced = Tasapainotettu @@ -526,96 +526,96 @@ Buffer graphics commands (faster, input lag) = Buffer graphics commands (faster, BufferedRenderingRequired = Warning: This game is not compatible with "Skip buffer effects" Camera = Kamera Camera Device = Kameralaite -Cardboard Screen Size = Screen size (in % of the viewport) -Cardboard Screen X Shift = X shift (in % of the blank space) -Cardboard Screen Y Shift = Y shift (in % of the blank space) -Cardboard VR Settings = Google Cardboard VR settings +Cardboard Screen Size = Näyttökoko (näkymän koon prosentuaalinen osuus) +Cardboard Screen X Shift = X-siirtymä (tyhjän tilan prosentuaalinen osuus) +Cardboard Screen Y Shift = Y-siirtymä (tyhjän tilan prosentuaalinen osuus) +Cardboard VR Settings = Google Cardboard VR -asetukset Cheats = Huijaukset CPU Core = CPU core Debugging = Vian etsintä -DefaultCPUClockRequired = Warning: This game requires the CPU clock to be set to default. -Deposterize = Deposterize -Deposterize Tip = Fixes visual banding glitches in upscaled textures +DefaultCPUClockRequired = Varoitus: Tämä peli vaatii Prosessorin kellon olevan asetettu oletusarvoon. +Deposterize = Häiriöiden korjaus +Deposterize Tip = Korjaa visuaaliset viivamaiset häiriöt ylöskaalattujen tekstuurien yhteydessä. Device = Laite Direct3D 9 = Direct3D 9 Direct3D 11 = Direct3D 11 Disabled = Poistettu käytöstä -Display Layout && Effects = Display layout && effects -Display Resolution (HW scaler) = Display resolution (HW scaler) -Enable Cardboard VR = Enable Cardboard VR +Display Layout && Effects = Näyttöasettelu ja -efektit +Display Resolution (HW scaler) = Näyttöresoluutio (Laitteiston skaalaaja) +Enable Cardboard VR = Ota käyttöön Cardboard VR FPS = FPS -Frame Rate Control = Framerate control -Frame Skipping = Ruudun ohitus -Frame Skipping Type = Frame skipping type +Frame Rate Control = Kuvataajuuden hallinta +Frame Skipping = Kuvan ohitus +Frame Skipping Type = Kuvan ohituksen tyyppi FullScreen = Koko näyttö -Geometry shader culling = Geometry shader culling -GPUReadbackRequired = Warning: This game requires "Skip GPU Readbacks" to be set to Off. -Hack Settings = Hack settings (may cause glitches) -Hardware Tessellation = Hardware tessellation +Geometry shader culling = Geometria-varjostimen karsinta +GPUReadbackRequired = Varoitus: Tämä peli vaatii, että "Ohita näytönohjaimen lukemat" on pois päältä. +Hack Settings = Hakkerointiasetukset (saattaa aiheutaa virheitä) +Hardware Tessellation = Laitteistotessellaatio Hardware Transform = Laitteistomuunnos -hardware transform error - falling back to software = Hardware transform error, falling back to software -HardwareTessellation Tip = Uses hardware to make curves -High = High +hardware transform error - falling back to software = Laitteistomuunnosvirhe, palataan ohjelmistotilaan +HardwareTessellation Tip = Käyttää laitteistoa muodostaakseen käyriä +High = Korkea Hybrid = Hybridi -Hybrid + Bicubic = Hybrid + Bicubic -Ignore camera notch when centering = Ignore camera notch when centering -Integer scale factor = Integer scale factor -Internal Resolution = Internal resolution -Lazy texture caching = Lazy texture caching (speedup) -Lazy texture caching Tip = Faster, but can cause text problems in a few games +Hybrid + Bicubic = Hybridi + Bicubic +Ignore camera notch when centering = Ohita kameran lovi keskittäessä +Integer scale factor = Kokonaislukuskaalauksen kerroin +Internal Resolution = Sisäinen resoluutio +Lazy texture caching = Laiska tekstuurivarastointi (nopeutus) +Lazy texture caching Tip = Nopeampi, mutta voi aiheuttaa tekstiongelmia joissakin peleissä Linear = Lineaarinen -Low = Low -LowCurves = Spline/Bezier curves quality -LowCurves Tip = Only used by some games, controls smoothness of curves -Lower resolution for effects (reduces artifacts) = Lower resolution for effects (reduces artifacts) -Manual Scaling = Manual scaling -Medium = Medium +Low = Matala +LowCurves = Spline/Bezier-käyrien laatu +LowCurves Tip = Käytetään vain joissakin peleissä, ohjaa käyrien sileyttä +Lower resolution for effects (reduces artifacts) = Alempi resoluutio efekteille (vähentää häiriöitä) +Manual Scaling = Manuaalinen skaalaus +Medium = Keskitaso Mode = Mode -Must Restart = You must restart PPSSPP for this change to take effect. -Native device resolution = Native device resolution +Must Restart = Sinun täytyy käynnistää PPSSPP uudelleen, jotta tämä muutos astuisi voimaan. +Native device resolution = Laiteen alkuperäinen resoluutio Nearest = Lähin -No buffer = No buffer +No buffer = Ei puskuria Show Battery % = Näytä akku % Show Speed = Näytä nopeus -Skip Buffer Effects = Skip buffer effects +Skip Buffer Effects = Ohita puskuriefektit None = Ei mitään -Number of Frames = Number of frames +Number of Frames = Kuvien määrä Off = Pois OpenGL = OpenGL Overlay Information = Overlay information -Partial Stretch = Partial stretch -Percent of FPS = Percent of FPS -Performance = Performance -Postprocessing shaders = Postprocessing shaders +Partial Stretch = Osittainen venytys +Percent of FPS = Kuvataajuden prosentti +Performance = Suorituskyky +Postprocessing shaders = Jälkikäsittelyvarjostimet Recreate Activity = Recreate activity -Render duplicate frames to 60hz = Render duplicate frames to 60 Hz -RenderDuplicateFrames Tip = Can make framerate smoother in games that run at lower framerates +Render duplicate frames to 60hz = Renderöi kuvat tuplana 60 Hz:siin +RenderDuplicateFrames Tip = Saattaa tehdä kuvataajuudesta tasaisemman peleissä, jotka toimivat matalammalla kuvataajuudella Rendering Mode = Renderöinti tapa Rendering Resolution = Renderöinti resoluutio -RenderingMode NonBuffered Tip = Nopeampi, mutta mitään ei välttämättä piirry joissakin peleissä +RenderingMode NonBuffered Tip = Nopeampi, mutta kaikki ei välttämättä piirry joissakin peleissä Rotation = Kierto Safe = Turvallinen Screen Scaling Filter = Näytön skaalauksen suodatin Show Debug Statistics = Näytä virheenkorjaustilastot -Show FPS Counter = Näytä FPS-laskuri -Skip GPU Readbacks = Skip GPU Readbacks +Show FPS Counter = Näytä kuvalaskuri (FPS) +Skip GPU Readbacks = Ohita GPU-lukemat Software Rendering = Ohjelmistopohjainen renderointi (kokeellinen) -Software Skinning = Software skinning -SoftwareSkinning Tip = Combine skinned model draws on the CPU, faster in most games +Software Skinning = Ohjelmistopohjainen muokkaus (skinning) +SoftwareSkinning Tip = Yhdistää skinnattujen mallien piirtämisen prosessorilla, nopeampi useimmissa peleissä Speed = Nopeus -Speed Hacks = Nopeus kikkoja (saattaa aiheuttaa renderöinti virheitä!) +Speed Hacks = Nopeuskikat (saattaa aiheuttaa renderöinti virheitä!) Stereo display shader = Stereo display shader -Stereo rendering = Stereo renderöinti +Stereo rendering = Stereorenderöinti Stretch = Venytä Texture Filter = Tekstuurisuodatin Texture Filtering = Tekstuurin suodatus -Texture replacement pack activated = Texture replacement pack activated +Texture replacement pack activated = Tekstuurin korvauspaketti aktivoidu Texture Scaling = Tekstuurin skaalaus Texture Shader = Tekstuurin varjostin -Turn off Hardware Tessellation - unsupported = Turn off "hardware tessellation": unsupported +Turn off Hardware Tessellation - unsupported = Ota pois käytöstä "laitteistotessellaatio": ei tuettu Unlimited = Rajoittamaton -Up to 1 = 1 asti -Up to 2 = 2 asti +Up to 1 = Enintään 1 +Up to 2 = Enintään 2 Upscale Level = Ylösskaalauksen taso Upscale Type = Ylösskaalauksen tapa UpscaleLevel Tip = Prosessorille raskas - joillekkin skaalauksille voidaan antaa viivettä nykimisen välttämiseksi @@ -638,19 +638,19 @@ Zip archive corrupt = ZIP-arkisto on korruptoitunut Zip file does not contain PSP software = ZIP-tiedosto ei sisällä PSP-ohjelmistoa [KeyMapping] -Allow combo mappings = Allow combo mappings -Autoconfigure = Auto configure -Autoconfigure for device = Autoconfigure for device -Bind All = Bind All -Clear All = Clear all -Combo mappings are not enabled = Combo mappings are not enabled -Default All = Restore defaults -Map a new key for = Map a new key for -Map Key = Map key -Map Mouse = Map mouse -Replace = Replace -Show PSP = Show PSP -You can press ESC to cancel. = You can press Esc to cancel. +Allow combo mappings = Salli yhdistelmäkartoitukset +Autoconfigure = Automaattinen määritys +Autoconfigure for device = Automaattinen määritys laitteelle +Bind All = Kartoita kaikki +Clear All = Tyhjennä kaikki +Combo mappings are not enabled = Yhdistelmäkuvaukset eivät ole käytössä +Default All = Palauta oletusasetukset +Map a new key for = Kartoita uusi näppäin kohteelle +Map Key = Kartoita näppäin +Map Mouse = Kartoita hiiri +Replace = Korvaa +Show PSP = Näytä PSP +You can press ESC to cancel. = Voit peruuttaa painamalla Esc. [MainMenu] Browse = Selaa... @@ -669,12 +669,12 @@ Load = Lataa... Loading... = Ladataan... PinPath = PinPath PPSSPP can't load games or save right now = PPSSPP ei voi ladata pelejä tai tallentaa juuri nyt -Recent = Äskettäin +Recent = Viimeisimmät SavesAreTemporary = PPSSPP tallentaa tilapäiseen tallennustilaan SavesAreTemporaryGuidance = Pura PPSSPP jonnekkin tallentaaksesi pysyvästi SavesAreTemporaryIgnore = Jätä varoitus huomiotta -UnpinPath = Unpin -UseBrowseOrLoad = Use Browse to choose a folder, or Load to choose a file. +UnpinPath = Poista kiinnitetty polku +UseBrowseOrLoad = Käytä Selaa valitaksesi kansio tai Lataa valitaksesi tiedosto. www.ppsspp.org = www.ppsspp.org [MainSettings] @@ -687,108 +687,108 @@ System = Järjestelmä Tools = Työkalut [MappableControls] -Alt speed 1 = Alt speed 1 -Alt speed 2 = Alt speed 2 -An.Down = Analog Down -An.Left = Analog Left -An.Right = Analog Right -An.Up = Analog Up -Analog limiter = Analog limiter -Analog speed = Analog speed -Analog Stick = Analog stick -Audio/Video Recording = Audio/Video recording -AxisSwap = Axis swap -Circle = Circle -Cross = Cross -Custom %d = Custom %d -D-pad down = D-pad down -D-pad left = D-pad left -D-pad right = D-pad right -D-pad up = D-pad up -DevMenu = DevMenu -Display Landscape = Display Landscape -Display Landscape Reversed = Display Landscape Reversed -Display Portrait = Display Portrait -Display Portrait Reversed = Display Portrait Reversed +Alt speed 1 = Vaihtoehtoinen nopeus 1 +Alt speed 2 = Vaihtoehtoinen nopeus 2 +An.Down = Analoginen alas +An.Left = Analoginen vasen +An.Right = Analoginen oikea +An.Up = Analoginen ylös +Analog limiter = Analoginen rajoitin +Analog speed = Analoginen nopeus +Analog Stick = Analoginen sauva +Audio/Video Recording = Ääni/video nauhoitus +AxisSwap = Akselinvaihto +Circle = Ympyrä +Cross = Risti +Custom %d = Mukautettu %d +D-pad down = Ristiohjain alas +D-pad left = Ristiohjain vasen +D-pad right = Ristiohjain oikea +D-pad up = Ristiohjain ylös +DevMenu = Kehittäjävalikko +Display Landscape = Näytä vaakakuva +Display Landscape Reversed = Näytä vaakakuva käänteisenä +Display Portrait = Näytä pystykuva +Display Portrait Reversed = Näytä pystykuva käänteisenä Double tap button = Double tap button -Down = Dpad Down -Dpad = Dpad -Frame Advance = Frame advance -Hold = Hold +Down = Alas +Dpad = Ristiohjain +Frame Advance = Kuva eteenpäin +Hold = Pidä Home = Home L = L -Left = Dpad Left +Left = Vasemalle Load State = Lataa tila Mute toggle = Mute toggle -Next Slot = Next Slot -None = None +Next Slot = Seuraava lohko +None = Ei mitään Note = Note -OpenChat = Open chat +OpenChat = Avaa keskustelu Pause = Pause R = R -RapidFire = Rapid-fire -Record = Record +RapidFire = Nopea tulitus +Record = Nauhoita Remote hold = Remote hold -Rewind = Rewind -Right = Dpad Right -Right Analog Stick = Right Analog Stick -RightAn.Down = RightAn.Down -RightAn.Left = RightAn.Left -RightAn.Right = RightAn.Right -RightAn.Up = RightAn.Up -Rotate Analog (CCW) = Rotate analog (CCW) -Rotate Analog (CW) = Rotate analog (CW) +Rewind = Takaisinkelaus +Right = Oikealle +Right Analog Stick = Oikea analoginen sauva +RightAn.Down = OikeaAn.Alas +RightAn.Left = OikeaAn.Vasen +RightAn.Right = OikeaAn.Oikea +RightAn.Up = OikeaAn.Ylös +Rotate Analog (CCW) = Käännä analogista vastapäivään +Rotate Analog (CW) = Käännä analogista myötäpäivään Save State = Tallena tila -Screen = Screen -Screenshot = Screenshot +Screen = Näyttö +Screenshot = Kuvakaappaus Select = Select -SpeedToggle = Speed toggle -Square = Square +SpeedToggle = Nopeuden vaihto +Square = Neliö Start = Start -Swipe Down = Swipe Down -Swipe Left = Swipe Left -Swipe Right = Swipe Right -Swipe Up = Swipe Up -tap to customize = tap to customize -Texture Dumping = Texture dumping -Texture Replacement = Texture replacement -Toggle Fullscreen = Toggle fullscreen +Swipe Down = Pyyhkäise alas +Swipe Left = Pyyhkäise vasemmalle +Swipe Right = Pyyhkäise oikealle +Swipe Up = Pyyhkäise ylös +tap to customize = kosketa mukauttaaksesi +Texture Dumping = Tekstuurien tallentaminen +Texture Replacement = Tekstuurien korvaus +Toggle Fullscreen = Vaihda koko ruudun tilan välillä Toggle mode = Toggle mode -Triangle = Triangle -Fast-forward = Fast-forward -Up = Dpad Up +Triangle = Kolmio +Fast-forward = Pikakelaus +Up = Ylös Vol + = Vol + Vol - = Vol - Wlan = WLAN [MemStick] -Already contains PSP data = Already contains PSP data -Create or Choose a PSP folder = Create or Choose a PSP folder -Current = Current -DataCanBeShared = Data can be shared between PPSSPP regular/Gold -DataCannotBeShared = Data CANNOT be shared between PPSSPP regular/Gold! -DataWillBeLostOnUninstall = Warning! Data will be lost when you uninstall PPSSPP! -DataWillStay = Data will stay even if you uninstall PPSSPP. -Done! = Done! -EasyUSBAccess = Easy USB access -Failed to move some files! = Failed to move some files! -Failed to save config = Failed to save config -Free space = Free space -Manually specify PSP folder = Manually specify PSP folder -MemoryStickDescription = Choose where to keep PSP data (Memory Stick) -Move Data = Move Data +Already contains PSP data = Sisältää jo PSP-tiedot +Create or Choose a PSP folder = Luo tai valitse PSP-kansio +Current = Nykyinen +DataCanBeShared = Tiedot voidaan jakaa PPSSPP:n tavallisen / Gold-version välillä +DataCannotBeShared = Tietoja EI VOIDA jakaan PPSSPP:n tavallisen / Gold-version välillä! +DataWillBeLostOnUninstall = Varoitus! Tiedot häviävät, kun poistat PPSSPP:n! +DataWillStay = Tiedot säilyvät jopa jos poistat PPSSPP:n. +Done! = Valmis! +EasyUSBAccess = Helppo USB-pääsy +Failed to move some files! = Joitain tiedostoja ei voitu siirtää! +Failed to save config = Asetusten tallentaminen epäonnistui +Free space = Vapaa tila +Manually specify PSP folder = Määritä PSP-kansio manuaalisesti +MemoryStickDescription = Valitse minne haluat säilyttää PSP-tiedot (muistikortti) +Move Data = Siirrä tiedot Selected PSP Data Folder = Selected PSP Data Folder -No data will be changed = No data will be changed -PPSSPP will restart after the change = PPSSPP will restart after the change -Skip for now = Skip for now -Starting move... = Starting move... -That folder doesn't work as a memstick folder. = That folder doesn't work as a memstick folder. -USBAccessThrough = USB access through Android/data/org.ppsspp.ppsspp/files -USBAccessThroughGold = USB access through Android/data/org.ppsspp.ppssppgold/files -Use App Private Data = Use App Private Data -Use PSP folder at root of storage = Use PSP folder at root of storage -Welcome to PPSSPP! = Welcome to PPSSPP! -WhatsThis = What's this? +No data will be changed = Mitään tietoja ei muuteta +PPSSPP will restart after the change = PPSSPP käynnistyy uudelleen muutoksen jälkeen +Skip for now = Ohita nyt +Starting move... = Siirto alkaa... +That folder doesn't work as a memstick folder. = Kyseinen kansio ei toimi muistikorttikansiona. +USBAccessThrough = USB-pääsy Android/data/org.ppsspp.ppsspp/files -kautta +USBAccessThroughGold = USB-pääsy Android/data/org.ppsspp.ppssppgold/files -kautta +Use App Private Data = Käytä sovelluksen yksityistä dataa +Use PSP folder at root of storage = Käytä PSP-kansiota tallennustilan juuressa +Welcome to PPSSPP! = Tervetuloa PPSSPP:n! +WhatsThis = Mikä tämä on? [Networking] AdHoc Server = Ad hoc server @@ -934,32 +934,32 @@ VideoSmoothingAA = VideoSmoothingAA Vignette = Vignette [PSPCredits] -all the forum mods = Kaikki foorumin moderaattorit -build server = build server -Buy Gold = Buy Gold -check = Also check out Dolphin, the best Wii/GC emu around: -CheckOutPPSSPP = Check out PPSSPP, the awesome PSP emulator: https://www.ppsspp.org/ -contributors = Contributors: +all the forum mods = kaikki foorumin moderaattorit +build server = koontipalvelin +Buy Gold = Osta Gold +check = Tutustu myös Dolphiniin, parhaaseen Wii/GC-emulaattoriin: +CheckOutPPSSPP = Tutustu PPSSPP:ään, mahtavaan PSP-emulaattoriin: https://www.ppsspp.org/ +contributors = Osallistujat: created = Luonut Discord = Discord -info1 = PPSSPP is intended for educational purposes only. -info2 = Please make sure that you own the rights to any games -info3 = you play by owning the UMD or by buying the digital -info4 = download from the PSN store on your real PSP. -info5 = PSP is a trademark by Sony, Inc. -iOS builds = iOS builds -license = Ilmainen ohjelmisto GPL 2.0+ -list = compatibility lists, forums, and development info -PPSSPP Forums = PPSSPP Forums -Privacy Policy = Privacy policy -Share PPSSPP = Share PPSSPP -specialthanks = Special thanks to: -specialthanksKeithGalocy = at NVIDIA (hardware, advice) -specialthanksMaxim = for his amazing Atrac3+ decoder work -testing = testing -this translation by = this translation by: -title = A fast and portable PSP emulator -tools = Free tools used: +info1 = PPSSPP on tarkoitettu vain opetustarkoituksiin. +info2 = Varmista, että omistat oikeudet kaikkiin peleihin, +info3 = joita pelaat omistamalla UMD:n tai ostamalla digitaalisen +info4 = latauksen PSN-kaupasta oikealla PSP:lläsi. +info5 = PSP on Sony, Inc:n tavaramerkki. +iOS builds = iOS buildit +license = Ilmainen ohjelmisto, GPL 2.0+ +list = yhteensopivuuslistat, foorumit ja kehitystiedot +PPSSPP Forums = PPSSPP-foorumit +Privacy Policy = Tietosuojakäytäntö +Share PPSSPP = Jaa PPSSPP +specialthanks = Erityiskiitokset: +specialthanksKeithGalocy = NVIDIA:lle (laitteisto, neuvot) +specialthanksMaxim = hänen uskomattomasta Atrac3+ -dekooderityöstään +testing = testaus +this translation by = tämän käännöksen teki: +title = Nopea ja helposti siirrettävä PSP-emulaattori +tools = Vapaita käytettyjä työkaluja: # Add translators or contributors who translated PPSSPP into your language here. # Add translators1-6 for up to 6 lines of translator credits. # Leave extra lines blank. 4 contributors per line seems to look best. @@ -970,28 +970,28 @@ translators4 = translators5 = translators6 = Twitter @PPSSPP_emu = Twitter @PPSSPP_emu -website = Check out the website: -written = Written in C++ for speed and portability +website = Tutustu verkkosivustoon: +written = Kirjoitettu C++:ssa nopeutta ja siirrettävyyttä varten [RemoteISO] -Browse Games = Browse games -Local Server Port = Local server port -Manual Mode Client = Manual mode client -Remote disc streaming = Remote disc streaming -Remote Port = Remote port -Remote Server = Remote server -Remote Subdirectory = Remote subdirectory -RemoteISODesc = Games in your recent list will be shared -RemoteISOLoading = Connected, loading game list... -RemoteISOScanning = Scanning... Click "share games" on your server device -RemoteISOScanningTimeout = Scanning... check your desktop's firewall settings -RemoteISOWifi = Note: Connect both devices to the same Wi-Fi network -RemoteISOWinFirewall = WARNING: Windows Firewall is blocking sharing -Settings = Settings -Share Games (Server) = Share games (server) -Share on PPSSPP startup = Share on PPSSPP startup -Stop Sharing = Stop sharing -Stopping.. = Stopping... +Browse Games = Selaa pelejä +Local Server Port = Paikallisen palvelimen portti +Manual Mode Client = Käsikäyttötilan asiakas +Remote disc streaming = Kauko-levyjen suoratoisto +Remote Port = Kaukoportti +Remote Server = Kaukopavelin +Remote Subdirectory = Kaukokansio +RemoteISODesc = Viimeksi käytettyjen pelien lista jaetaan +RemoteISOLoading = Yhdistetty, ladataan pelilistaa... +RemoteISOScanning = Etsitään... Valitse "jaa pelit" palvelinlaitteellasi +RemoteISOScanningTimeout = Etsitään... tarkista työpöydän palomuurin asetukset +RemoteISOWifi = Huomaa: Yhdistä molemmat laitteet samaan Wi-Fi-verkkoon +RemoteISOWinFirewall = VAROITUS: Windowsin palomuuri estää jakamisen +Settings = Asetukset +Share Games (Server) = Jaa pelejä (palvelin) +Share on PPSSPP startup = Jaa käynnistyksen yhteydessä +Stop Sharing = Lopeta jakaminen +Stopping.. = Lopetetaan... [Reporting] Bad = Bad @@ -1037,16 +1037,16 @@ Unselected Overall Description = How well does this game emulate? View Feedback = View all feedbacks [Savedata] -Date = Date -Filename = Filename -No screenshot = No screenshot -None yet. Things will appear here after you save. = None yet. Things will appear here after you save. -Nothing matching '%1' was found. = Nothing matching '%1' was found. -Save Data = Save data -Save States = Save states -Savedata Manager = Savedata manager -Showing matches for '%1'. = Showing matches for '%1'. -Size = Size +Date = Päivämäärä +Filename = Tiedostonimi +No screenshot = Ei kuvakaappausta +None yet. Things will appear here after you save. = Ei vielä. Tähän ilmestyy asioita tallentamisen jälkeen. +Nothing matching '%1' was found. = Ei tuloksia haulla '%1'. +Save Data = Tallennustiedosto +Save States = Tilatallennukset +Savedata Manager = Tallennustiedoston hallinta +Showing matches for '%1'. = Näytetään tulokset haulle '%1'. +Size = Koko [Screen] Cardboard VR OFF = Cardboard VR off @@ -1078,22 +1078,22 @@ State load undone = State load undone Untitled PSP game = Untitled PSP game [Search] -Clear filter = Clear filter -Filter = Filter -Filtering settings by '%1' = Filtering settings by '%1' -Find settings = Find settings -No settings matched '%1' = No settings matched '%1' -Search term = Search term +Clear filter = Tyhjennä suodatin +Filter = Suodata +Filtering settings by '%1' = Suodatetaan asetuksia hakusanalla '%1' +Find settings = Etsi asetuksia +No settings matched '%1' = Ei löytynyt asetuksia hakusanalla '%1' +Search term = Hakusana [Store] -Already Installed = Already installed -Connection Error = Connection error -Install = Install -Launch Game = Launch game -Loading... = Loading... -MB = MB -Size = Size -Uninstall = Uninstall +Already Installed = Jo asennettu +Connection Error = Yhteysvirhe +Install = Asenna +Launch Game = Käynnistä peli +Loading... = Ladataan... +MB = Mt +Size = Koko +Uninstall = Poista asennus [SysInfo] %0.2f Hz = %0.2f Hz @@ -1164,29 +1164,29 @@ Vulkan Extensions = Vulkan extensions Vulkan Features = Vulkan features [System] -(broken) = (broken) +(broken) = (rikki) 12HR = 12HR 24HR = 24HR -Auto = Auto -Auto Load Savestate = Auto load savestate -AVI Dump started. = AVI dump started -AVI Dump stopped. = AVI dump stopped -Cache ISO in RAM = Cache full ISO in RAM -Change CPU Clock = Change emulated PSP's CPU clock (unstable) -Color Saturation = Color Saturation -Color Tint = Color Tint +Auto = Automaattinen +Auto Load Savestate = Lataa tilatallennus automaattisesti +AVI Dump started. = AVI-tallennus aloitettu. +AVI Dump stopped. = AVI-tallennus lopetettu. +Cache ISO in RAM = Lataa ISO-tiedosto RAM-muistiin +Change CPU Clock = Muuta emuloidun PSP:n näytönohjaimen kelloa (epävakaa) +Color Saturation = Värisaturaatio +Color Tint = Värin sävy Error: load undo state is from a different game = Error: load undo state is from a different game Failed to load state for load undo. Error in the file system. = Failed to load state for load undo. Error in the file system. Floating symbols = Floating symbols Game crashed = Peli kaatui Language = Kieli -Memory Stick folder = Memory Stick folder -Memory Stick size = Memory Stick size +Memory Stick folder = Muistikortin kansio +Memory Stick size = Muistikortin koko Change Nickname = Vaihda nimimerkkiä ChangingMemstickPath = Save games, save states, and other data will not be copied to this folder.\n\nChange the Memory Stick folder? ChangingMemstickPathInvalid = That path couldn't be used to save Memory Stick files. -Cheats = Cheats -Clear Recent = Clear "Recent" +Cheats = Huijaukset +Clear Recent = Tyhjennä "Viimeisimmät" Clear Recent Games List = Clear Recent games list Clear UI background = Clear UI background Confirmation Button = Confirmation button @@ -1200,92 +1200,92 @@ Display Games on a grid = Display "Games" on a grid Display Homebrew on a grid = Display "Homebrew && Demos" on a grid Display Recent on a grid = Display "Recent" on a grid Dynarec (JIT) = Dynarec (JIT) -Emulation = Emulation +Emulation = Emulaatio Enable Cheats = Ota huijaukset käyttöön Enable Compatibility Server Reports = Ota yhteensopivat palvelinilmoitukset käyttöön -Failed to load state. Error in the file system. = Failed to load state. Error in the file system. -Failed to save state. Error in the file system. = Failed to save state. Error in the file system. -Fast (lag on slow storage) = Fast (lag on slow storage) -Fast Memory = Nopea muisti (epävakaa) -Force real clock sync (slower, less lag) = Force real clock sync (slower, less lag) -Games list settings = Games list settings -General = General -Grid icon size = Grid icon size -Help the PPSSPP team = Help the PPSSPP team -Host (bugs, less lag) = Host (bugs, less lag) -Ignore bad memory accesses = Ignore bad memory accesses -Increase size = Increase size -Interpreter = Interpreter -IO timing method = I/O timing method -IR Interpreter = IR interpreter -Memory Stick Folder = Memory Stick folder -Memory Stick inserted = Memory Stick inserted -MHz, 0:default = MHz, 0 = default -MMDDYYYY = MMDDYYYY -Moving background = Moving background -Newest Save = Newest save -No animation = No animation -Not a PSP game = Not a PSP game -Off = Off -Oldest Save = Oldest save -Path does not exist! = Path does not exist! -PSP Memory Stick = PSP Memory Stick -PSP Model = PSP model -PSP Settings = PSP settings +Failed to load state. Error in the file system. = Tilatallennuksen lataus epäonnistui. Virhe tiedostojärjestelmässä. +Failed to save state. Error in the file system. = Tilatallennuksen luonti epäonnistui. Virhe tiedostojärjestelmässä. +Fast (lag on slow storage) = Nopea (viive hidas tallennustilassa) +Fast Memory = Nopea muisti +Force real clock sync (slower, less lag) = Pakota todellinen kellon synkronointi (hidastaa, vähemmän viivettä) +Games list settings = Peliluettelon asetukset +General = Yleiset +Grid icon size = Ruudukon kuvakkeen koko +Help the PPSSPP team = Auta PPSSPP-tiimiä +Host (bugs, less lag) = Isäntä (bugit, vähemmän viivettä) +Ignore bad memory accesses = Ohita huonot muistipääsyt +Increase size = Kasvata kokoa +Interpreter = Tulkki +IO timing method = I/O-aikamenetelmä +IR Interpreter = IR-tulkki +Memory Stick Folder = Muistikortin kansio +Memory Stick inserted = Muistikortin asetettu +MHz, 0:default = MHz, 0 = oletus +MMDDYYYY = KKPPVVVV +Moving background = Liikkuva tausta +Newest Save = Uusin tallennus +No animation = Ei animaatiota +Not a PSP game = Ei PSP-peli +Off = Pois +Oldest Save = Vanhin tallennus +Path does not exist! = Polkua ei ole olemassa! +PSP Memory Stick = PSP-muistikortti +PSP Model = PSP-malli +PSP Settings = PSP-asetukset PSP-1000 = PSP-1000 PSP-2000/3000 = PSP-2000/3000 -Recent games = Recent games -Record Audio = Record audio -Record Display = Record display -Reset Recording on Save/Load State = Reset recording on Save/Load state -Restore Default Settings = Restore PPSSPP's settings to default -Rewind Snapshot Interval = Rewind Snapshot Interval (mem hog) -Save path in installed.txt = Save path in installed.txt -Save path in My Documents = Save path in My Documents -Savestate Slot = Savestate slot -Savestate slot backups = Savestate slot backups -Screenshots as PNG = Save screenshots in PNG format -Set Memory Stick folder = Set Memory Stick folder -Set UI background... = Set UI background... -Show ID = Show ID -Show Memory Stick folder = Show Memory Stick folder -Show region flag = Show region flag -Simulate UMD delays = Simulate UMD delays -Slot 1 = Slot 1 -Slot 2 = Slot 2 -Slot 3 = Slot 3 -Slot 4 = Slot 4 -Slot 5 = Slot 5 -Storage full = Storage full -Sustained performance mode = Sustained performance mode -Theme = Theme -Time Format = Time format -Transparent UI background = Transparent UI background -UI = UI -UI background animation = UI background animation -UI Sound = UI sound -undo %c = backup %c +Recent games = Viimeisimmät pelit +Record Audio = Nauhoita ääntä +Record Display = Nauhoita näyttöä +Reset Recording on Save/Load State = Nollaa nauhoitus tallennettaessa/ladattaessa tila +Restore Default Settings = Palauta PPSSPP:n oletusasetukset +Rewind Snapshot Interval = Pikakelaa tilannevedosten välit (muistisyöppö) +Save path in installed.txt = Tallennuskansio installed.txt-tiedostossa +Save path in My Documents = Tallennuskansio Omat tiedostot -kansiossa +Savestate Slot = Tilatallennuksen lohko +Savestate slot backups = Tallennustilan paikan varmuuskopiot +Screenshots as PNG = Tallenna kuvankaappaukset PNG-muodossa +Set Memory Stick folder = Aseta muistikortin kansio +Set UI background... = Aseta käyttöliittymän tausta... +Show ID = Näytä tunniste +Show Memory Stick folder = Näytä muistikortin kansio +Show region flag = Näytä alueen lippu +Simulate UMD delays = Simuloi UMD-viiveet +Slot 1 = Lohko 1 +Slot 2 = Lohko 2 +Slot 3 = Lohko 3 +Slot 4 = Lohko 4 +Slot 5 = Lohko 5 +Storage full = Tallennustila täynnä +Sustained performance mode = Jatkuva suorituskykytila +Theme = Teema +Time Format = Ajan muoto +Transparent UI background = Läpinäkyvä käyttöliittymän tausta +UI = Käyttöliittymä +UI background animation = Käyttöliittymän taustan animaatio +UI Sound = Käyttöliittymän äänet +undo %c = peruuta %c USB = USB -Use Lossless Video Codec (FFV1) = Use lossless video codec (FFV1) -Use O to confirm = Use O as confirmation button +Use Lossless Video Codec (FFV1) = Käytä häviöttömän videon koodekkia (FFV1) +Use O to confirm = Käytä O-näppäintä vahvistukseen Use output buffer (with overlay) for recording = Use output buffer (with overlay) for recording -Use system native keyboard = Use system native keyboard -Use X to confirm = Use X as confirmation button -VersionCheck = Check for new versions of PPSSPP -WARNING: Android battery save mode is on = WARNING: Android battery save mode is On -WARNING: Battery save mode is on = WARNING: Battery save mode is on -Waves = Waves -YYYYMMDD = YYYYMMDD +Use system native keyboard = Käytä järjestelmän omaa näppäimistöä +Use X to confirm = Käytä X-näppäintä vahvistukseen +VersionCheck = Tarkista uudet PPSSPP-versiot +WARNING: Android battery save mode is on = VAROITUS: Androidin akun säästötila on käytössä +WARNING: Battery save mode is on = VAROITUS: Akun säästötila on käytössä +Waves = Aallot +YYYYMMDD = VVVVKKPP [TextureShaders] -Off = Off +Off = Pois TexMMPX = TexMMPX Tex2xBRZ = Tex2xBRZ Tex4xBRZ = Tex4xBRZ [Themes] -Dark = Dark -Default = Default +Dark = Tumma +Default = Oletus [UI Elements] %1 button = %1 button From 48586ed0ad994a9dcb1a32e669bdf3c8491964cb Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 27 Jul 2023 23:28:19 -0700 Subject: [PATCH 122/167] irjit: Combine Load32Left/Right even on unaligned. This helps on devices that don't allow unaligned load/store. --- Core/MIPS/IR/IRPassSimplify.cpp | 38 ++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 52ea66c5d4..3d277ebbae 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -228,7 +228,7 @@ bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions }; auto combineOpposite = [&](IROp matchOp, int matchOff, IROp replaceOp, int replaceOff) { - if (!opts.unalignedLoadStore || i + 1 >= n) + if (i + 1 >= n) return false; const IRInst &next = nextOp(); if (next.op != matchOp || next.dest != inst.dest || next.src1 != inst.src1) @@ -236,8 +236,40 @@ bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions if (inst.constant + matchOff != next.constant) return false; - // Write out one unaligned op. - out.Write(replaceOp, inst.dest, inst.src1, out.AddConstant(inst.constant + replaceOff)); + if (opts.unalignedLoadStore) { + // Write out one unaligned op. + out.Write(replaceOp, inst.dest, inst.src1, out.AddConstant(inst.constant + replaceOff)); + } else if (replaceOp == IROp::Load32) { + // We can still combine to a simpler set of two loads. + // We start by isolating the address and shift amount. + + // IRTEMP_LR_ADDR = rs + imm + out.Write(IROp::AddConst, IRTEMP_LR_ADDR, inst.src1, out.AddConstant(inst.constant + replaceOff)); + // IRTEMP_LR_SHIFT = (addr & 3) * 8 + out.Write(IROp::AndConst, IRTEMP_LR_SHIFT, IRTEMP_LR_ADDR, out.AddConstant(3)); + out.Write(IROp::ShlImm, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, 3); + // IRTEMP_LR_ADDR = addr & 0xfffffffc + out.Write(IROp::AndConst, IRTEMP_LR_ADDR, IRTEMP_LR_ADDR, out.AddConstant(0xFFFFFFFC)); + // IRTEMP_LR_VALUE = low_word, dest = high_word + out.Write(IROp::Load32, inst.dest, IRTEMP_LR_ADDR, out.AddConstant(0)); + out.Write(IROp::Load32, IRTEMP_LR_VALUE, IRTEMP_LR_ADDR, out.AddConstant(4)); + + // Now we just need to adjust and combine dest and IRTEMP_LR_VALUE. + // inst.dest >>= shift (putting its bits in the right spot.) + out.Write(IROp::Shr, inst.dest, inst.dest, IRTEMP_LR_SHIFT); + // We can't shift by 32, so we compromise by shifting twice. + out.Write(IROp::ShlImm, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, 8); + // IRTEMP_LR_SHIFT = 24 - shift + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + // IRTEMP_LR_VALUE <<= (24 - shift) + out.Write(IROp::Shl, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_SHIFT); + + // At this point the values are aligned, and we just merge. + out.Write(IROp::Or, inst.dest, inst.dest, IRTEMP_LR_VALUE); + } else { + return false; + } // Skip the next one, replaced. i++; return true; From df2462b1d95410893f9920156bb2dbbe24bec1bb Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 17:51:16 -0700 Subject: [PATCH 123/167] irjit: Implement ll/sc. These occur more than I expected in LittleBigPlanet while loading. --- Core/MIPS/ARM/ArmCompLoadStore.cpp | 6 ++++++ Core/MIPS/ARM/ArmJit.h | 1 + Core/MIPS/ARM64/Arm64CompLoadStore.cpp | 6 ++++++ Core/MIPS/ARM64/Arm64Jit.h | 1 + Core/MIPS/IR/IRCompLoadStore.cpp | 24 ++++++++++++++++++++++++ Core/MIPS/IR/IRFrontend.h | 1 + Core/MIPS/IR/IRInst.cpp | 2 ++ Core/MIPS/IR/IRInst.h | 2 ++ Core/MIPS/IR/IRInterpreter.cpp | 15 +++++++++++++++ Core/MIPS/IR/IRPassSimplify.cpp | 6 +++++- Core/MIPS/JitCommon/JitCommon.h | 1 + Core/MIPS/MIPS/MipsJit.h | 1 + Core/MIPS/MIPSTables.cpp | 4 ++-- Core/MIPS/RiscV/RiscVCompLoadStore.cpp | 12 ++++++++++++ Core/MIPS/RiscV/RiscVJit.cpp | 5 +++++ Core/MIPS/RiscV/RiscVJit.h | 1 + Core/MIPS/fake/FakeJit.h | 1 + Core/MIPS/x86/CompLoadStore.cpp | 6 ++++++ Core/MIPS/x86/Jit.h | 1 + 19 files changed, 93 insertions(+), 3 deletions(-) diff --git a/Core/MIPS/ARM/ArmCompLoadStore.cpp b/Core/MIPS/ARM/ArmCompLoadStore.cpp index 1c62d7f986..e4e9131a28 100644 --- a/Core/MIPS/ARM/ArmCompLoadStore.cpp +++ b/Core/MIPS/ARM/ArmCompLoadStore.cpp @@ -385,6 +385,12 @@ namespace MIPSComp } } + void ArmJit::Comp_StoreSync(MIPSOpcode op) { + CONDITIONAL_DISABLE(LSU); + + DISABLE; + } + void ArmJit::Comp_Cache(MIPSOpcode op) { CONDITIONAL_DISABLE(LSU); diff --git a/Core/MIPS/ARM/ArmJit.h b/Core/MIPS/ARM/ArmJit.h index 829f53ef7d..04f1ac79d3 100644 --- a/Core/MIPS/ARM/ArmJit.h +++ b/Core/MIPS/ARM/ArmJit.h @@ -61,6 +61,7 @@ public: // Ops void Comp_ITypeMem(MIPSOpcode op) override; + void Comp_StoreSync(MIPSOpcode op) override; void Comp_Cache(MIPSOpcode op) override; void Comp_RelBranch(MIPSOpcode op) override; diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index 08e7d541a6..9824f07001 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -434,6 +434,12 @@ namespace MIPSComp { } } + void Arm64Jit::Comp_StoreSync(MIPSOpcode op) { + CONDITIONAL_DISABLE(LSU); + + DISABLE; + } + void Arm64Jit::Comp_Cache(MIPSOpcode op) { CONDITIONAL_DISABLE(LSU); diff --git a/Core/MIPS/ARM64/Arm64Jit.h b/Core/MIPS/ARM64/Arm64Jit.h index 3f81cd0a75..f3d6acc6a7 100644 --- a/Core/MIPS/ARM64/Arm64Jit.h +++ b/Core/MIPS/ARM64/Arm64Jit.h @@ -62,6 +62,7 @@ public: // Ops void Comp_ITypeMem(MIPSOpcode op) override; + void Comp_StoreSync(MIPSOpcode op) override; void Comp_Cache(MIPSOpcode op) override; void Comp_RelBranch(MIPSOpcode op) override; diff --git a/Core/MIPS/IR/IRCompLoadStore.cpp b/Core/MIPS/IR/IRCompLoadStore.cpp index cb1892d3d2..1a4aac5069 100644 --- a/Core/MIPS/IR/IRCompLoadStore.cpp +++ b/Core/MIPS/IR/IRCompLoadStore.cpp @@ -105,6 +105,30 @@ namespace MIPSComp { } } + void IRFrontend::Comp_StoreSync(MIPSOpcode op) { + CONDITIONAL_DISABLE(LSU); + + int offset = _IMM16; + MIPSGPReg rt = _RT; + MIPSGPReg rs = _RS; + // Note: still does something even if loading to zero. + + CheckMemoryBreakpoint(rs, offset); + + switch (op >> 26) { + case 48: // ll + ir.Write(IROp::Load32Linked, rt, rs, ir.AddConstant(offset)); + break; + + case 56: // sc + ir.Write(IROp::Store32Conditional, rt, rs, ir.AddConstant(offset)); + break; + + default: + INVALIDOP; + } + } + void IRFrontend::Comp_Cache(MIPSOpcode op) { CONDITIONAL_DISABLE(LSU); diff --git a/Core/MIPS/IR/IRFrontend.h b/Core/MIPS/IR/IRFrontend.h index 0f9a1ab418..b53e053d73 100644 --- a/Core/MIPS/IR/IRFrontend.h +++ b/Core/MIPS/IR/IRFrontend.h @@ -18,6 +18,7 @@ public: // Ops void Comp_ITypeMem(MIPSOpcode op) override; + void Comp_StoreSync(MIPSOpcode op) override; void Comp_Cache(MIPSOpcode op) override; void Comp_RelBranch(MIPSOpcode op) override; diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 116908c656..2f160772f2 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -73,6 +73,7 @@ static const IRMeta irMeta[] = { { IROp::Load32, "Load32", "GGC" }, { IROp::Load32Left, "Load32Left", "GGC", IRFLAG_SRC3DST }, { IROp::Load32Right, "Load32Right", "GGC", IRFLAG_SRC3DST }, + { IROp::Load32Linked, "Load32Linked", "GGC" }, { IROp::LoadFloat, "LoadFloat", "FGC" }, { IROp::LoadVec4, "LoadVec4", "VGC" }, { IROp::Store8, "Store8", "GGC", IRFLAG_SRC3 }, @@ -80,6 +81,7 @@ static const IRMeta irMeta[] = { { IROp::Store32, "Store32", "GGC", IRFLAG_SRC3 }, { IROp::Store32Left, "Store32Left", "GGC", IRFLAG_SRC3 }, { IROp::Store32Right, "Store32Right", "GGC", IRFLAG_SRC3 }, + { IROp::Store32Conditional, "Store32Conditional", "GGC", IRFLAG_SRC3DST }, { IROp::StoreFloat, "StoreFloat", "FGC", IRFLAG_SRC3 }, { IROp::StoreVec4, "StoreVec4", "VGC", IRFLAG_SRC3 }, { IROp::FAdd, "FAdd", "FFF" }, diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index d4eb47f49a..5f2aaeeb33 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -92,6 +92,7 @@ enum class IROp : u8 { Load32, Load32Left, Load32Right, + Load32Linked, LoadFloat, LoadVec4, @@ -100,6 +101,7 @@ enum class IROp : u8 { Store32, Store32Left, Store32Right, + Store32Conditional, StoreFloat, StoreVec4, diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 561adf6f25..699f92ddff 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -218,6 +218,11 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { mips->r[inst->dest] = (mips->r[inst->dest] & destMask) | (mem >> shift); break; } + case IROp::Load32Linked: + if (inst->dest != MIPS_REG_ZERO) + mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + inst->constant); + mips->llBit = 1; + break; case IROp::LoadFloat: mips->f[inst->dest] = Memory::ReadUnchecked_Float(mips->r[inst->src1] + inst->constant); break; @@ -251,6 +256,16 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { Memory::WriteUnchecked_U32(result, addr & 0xfffffffc); break; } + case IROp::Store32Conditional: + if (mips->llBit) { + Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + inst->constant); + if (inst->dest != MIPS_REG_ZERO) { + mips->r[inst->dest] = 1; + } + } else if (inst->dest != MIPS_REG_ZERO) { + mips->r[inst->dest] = 0; + } + break; case IROp::StoreFloat: Memory::WriteUnchecked_Float(mips->f[inst->src3], mips->r[inst->src1] + inst->constant); break; diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 3d277ebbae..bfd31651a7 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -604,6 +604,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::Store32: case IROp::Store32Left: case IROp::Store32Right: + case IROp::Store32Conditional: if (gpr.IsImm(inst.src1) && inst.src1 != inst.dest) { gpr.MapIn(inst.dest); out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); @@ -627,6 +628,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::Load16: case IROp::Load16Ext: case IROp::Load32: + case IROp::Load32Linked: if (gpr.IsImm(inst.src1) && inst.src1 != inst.dest) { gpr.MapDirty(inst.dest); out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); @@ -1507,10 +1509,12 @@ bool ApplyMemoryValidation(const IRWriter &in, IRWriter &out, const IROptions &o break; case IROp::Load32: + case IROp::Load32Linked: case IROp::LoadFloat: case IROp::Store32: + case IROp::Store32Conditional: case IROp::StoreFloat: - addValidate(IROp::ValidateAddress32, inst, inst.op == IROp::Store32 || inst.op == IROp::StoreFloat); + addValidate(IROp::ValidateAddress32, inst, inst.op == IROp::Store32 || inst.op == IROp::Store32Conditional || inst.op == IROp::StoreFloat); break; case IROp::LoadVec4: diff --git a/Core/MIPS/JitCommon/JitCommon.h b/Core/MIPS/JitCommon/JitCommon.h index b5944c3db5..c88c9fe345 100644 --- a/Core/MIPS/JitCommon/JitCommon.h +++ b/Core/MIPS/JitCommon/JitCommon.h @@ -54,6 +54,7 @@ namespace MIPSComp { virtual void Comp_RunBlock(MIPSOpcode op) = 0; virtual void Comp_ReplacementFunc(MIPSOpcode op) = 0; virtual void Comp_ITypeMem(MIPSOpcode op) = 0; + virtual void Comp_StoreSync(MIPSOpcode op) = 0; virtual void Comp_Cache(MIPSOpcode op) = 0; virtual void Comp_RelBranch(MIPSOpcode op) = 0; virtual void Comp_RelBranchRI(MIPSOpcode op) = 0; diff --git a/Core/MIPS/MIPS/MipsJit.h b/Core/MIPS/MIPS/MipsJit.h index 195579b37d..9026ac185c 100644 --- a/Core/MIPS/MIPS/MipsJit.h +++ b/Core/MIPS/MIPS/MipsJit.h @@ -62,6 +62,7 @@ public: // Ops void Comp_ITypeMem(MIPSOpcode op) override {} + void Comp_StoreSync(MIPSOpcode op) override {} void Comp_Cache(MIPSOpcode op) override {} void Comp_RelBranch(MIPSOpcode op) override {} diff --git a/Core/MIPS/MIPSTables.cpp b/Core/MIPS/MIPSTables.cpp index 3a102f63f1..f012b0eab4 100644 --- a/Core/MIPS/MIPSTables.cpp +++ b/Core/MIPS/MIPSTables.cpp @@ -150,7 +150,7 @@ static const MIPSInstruction tableImmediate[64] = // xxxxxx ..... ..... ........ INSTR("swr", JITFUNC(Comp_ITypeMem), Dis_ITypeMem, Int_ITypeMem, IN_IMM16|IN_RS_ADDR|IN_RT|OUT_MEM|MEMTYPE_WORD), INSTR("cache", JITFUNC(Comp_Cache), Dis_Cache, Int_Cache, IN_MEM|IN_IMM16|IN_RS_ADDR), //48 - INSTR("ll", JITFUNC(Comp_Generic), Dis_Generic, Int_StoreSync, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_RT|OUT_OTHER|MEMTYPE_WORD), + INSTR("ll", JITFUNC(Comp_StoreSync), Dis_Generic, Int_StoreSync, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_RT|OUT_OTHER|MEMTYPE_WORD), INSTR("lwc1", JITFUNC(Comp_FPULS), Dis_FPULS, Int_FPULS, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_FT|MEMTYPE_FLOAT|IS_FPU), INSTR("lv.s", JITFUNC(Comp_SV), Dis_SV, Int_SV, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_OTHER|IS_VFPU|VFPU_NO_PREFIX|MEMTYPE_FLOAT), INVALID, @@ -159,7 +159,7 @@ static const MIPSInstruction tableImmediate[64] = // xxxxxx ..... ..... ........ INSTR("lv.q", JITFUNC(Comp_SVQ), Dis_SVQ, Int_SVQ, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_OTHER|IS_VFPU|VFPU_NO_PREFIX|MEMTYPE_VQUAD), //copU ENCODING(VFPU5), //56 - INSTR("sc", JITFUNC(Comp_Generic), Dis_Generic, Int_StoreSync, IN_IMM16|IN_RS_ADDR|IN_OTHER|IN_RT|OUT_RT|OUT_MEM|MEMTYPE_WORD), + INSTR("sc", JITFUNC(Comp_StoreSync), Dis_Generic, Int_StoreSync, IN_IMM16|IN_RS_ADDR|IN_OTHER|IN_RT|OUT_RT|OUT_MEM|MEMTYPE_WORD), INSTR("swc1", JITFUNC(Comp_FPULS), Dis_FPULS, Int_FPULS, IN_IMM16|IN_RS_ADDR|IN_FT|OUT_MEM|MEMTYPE_FLOAT|IS_FPU), //copU INSTR("sv.s", JITFUNC(Comp_SV), Dis_SV, Int_SV, IN_IMM16|IN_RS_ADDR|IN_OTHER|OUT_MEM|IS_VFPU|VFPU_NO_PREFIX|MEMTYPE_FLOAT), INVALID, diff --git a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp index a543236610..8cc8af74de 100644 --- a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp +++ b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp @@ -112,6 +112,10 @@ void RiscVJit::CompIR_Load(IRInst inst) { LW(gpr.R(inst.dest), addrReg, imm); break; + case IROp::Load32Linked: + CompIR_Generic(inst); + break; + default: INVALIDOP; break; @@ -249,6 +253,14 @@ void RiscVJit::CompIR_Store(IRInst inst) { } } +void RiscVJit::CompIR_CondStore(IRInst inst) { + CONDITIONAL_DISABLE; + if (inst.op != IROp::Store32Conditional) + INVALIDOP; + + CompIR_Generic(inst); +} + void RiscVJit::CompIR_StoreShift(IRInst inst) { CONDITIONAL_DISABLE; diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index b94369c4ca..9b8839a4eb 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -201,6 +201,7 @@ void RiscVJit::CompileIRInst(IRInst inst) { case IROp::Load16: case IROp::Load16Ext: case IROp::Load32: + case IROp::Load32Linked: CompIR_Load(inst); break; @@ -223,6 +224,10 @@ void RiscVJit::CompileIRInst(IRInst inst) { CompIR_Store(inst); break; + case IROp::Store32Conditional: + CompIR_CondStore(inst); + break; + case IROp::Store32Left: case IROp::Store32Right: CompIR_StoreShift(inst); diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index 02c90c735d..8a05fdb871 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -71,6 +71,7 @@ private: void CompIR_Breakpoint(IRInst inst); void CompIR_Compare(IRInst inst); void CompIR_CondAssign(IRInst inst); + void CompIR_CondStore(IRInst inst); void CompIR_Div(IRInst inst); void CompIR_Exit(IRInst inst); void CompIR_ExitIf(IRInst inst); diff --git a/Core/MIPS/fake/FakeJit.h b/Core/MIPS/fake/FakeJit.h index 7fe42a6981..bce7fe92d3 100644 --- a/Core/MIPS/fake/FakeJit.h +++ b/Core/MIPS/fake/FakeJit.h @@ -60,6 +60,7 @@ public: // Ops void Comp_ITypeMem(MIPSOpcode op) override {} + void Comp_StoreSync(MIPSOpcode op) override {} void Comp_Cache(MIPSOpcode op) override {} void Comp_RelBranch(MIPSOpcode op) override {} diff --git a/Core/MIPS/x86/CompLoadStore.cpp b/Core/MIPS/x86/CompLoadStore.cpp index feb627d3e8..9ef4b0b206 100644 --- a/Core/MIPS/x86/CompLoadStore.cpp +++ b/Core/MIPS/x86/CompLoadStore.cpp @@ -405,6 +405,12 @@ namespace MIPSComp { } + void Jit::Comp_StoreSync(MIPSOpcode op) { + CONDITIONAL_DISABLE(LSU); + + DISABLE; + } + void Jit::Comp_Cache(MIPSOpcode op) { CONDITIONAL_DISABLE(LSU); diff --git a/Core/MIPS/x86/Jit.h b/Core/MIPS/x86/Jit.h index 2e2f806fd8..0a56cfae5b 100644 --- a/Core/MIPS/x86/Jit.h +++ b/Core/MIPS/x86/Jit.h @@ -69,6 +69,7 @@ public: // Ops void Comp_ITypeMem(MIPSOpcode op) override; + void Comp_StoreSync(MIPSOpcode op) override; void Comp_Cache(MIPSOpcode op) override; void Comp_RelBranch(MIPSOpcode op) override; From f0d4267c5ea45311ccf6c009ab3f2a4ada789f83 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 17:51:52 -0700 Subject: [PATCH 124/167] HLE: Reset ll/sc link on any syscall. This seems to happen from any syscall, which makes sense. The bit isn't cleared on an sc, but a thread switch doesn't need to occur. --- Core/HLE/HLE.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp index ce7a04c345..363a809476 100644 --- a/Core/HLE/HLE.cpp +++ b/Core/HLE/HLE.cpp @@ -540,6 +540,9 @@ void HLEReturnFromMipsCall() { const static u32 deadbeefRegs[12] = {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF}; inline static void SetDeadbeefRegs() { + // Not exactly the same, but any time a syscall happens, it should clear ll. + currentMIPS->llBit = 0; + if (g_Config.bSkipDeadbeefFilling) return; From a5a2671af3ba2d32714616045139711fdf15dea7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 11:57:30 -0700 Subject: [PATCH 125/167] irjit: Implement vf2ix. Used in LittleBigPlanet when playing intro movies. --- Core/MIPS/IR/IRCompVFPU.cpp | 50 +++++++++++++++++++++++++++++++- Core/MIPS/IR/IRInst.cpp | 1 + Core/MIPS/IR/IRInst.h | 4 +-- Core/MIPS/IR/IRInterpreter.cpp | 28 +++++++++++++++++- Core/MIPS/IR/IRPassSimplify.cpp | 1 + Core/MIPS/RiscV/RiscVCompFPU.cpp | 1 + Core/MIPS/RiscV/RiscVJit.cpp | 1 + 7 files changed, 81 insertions(+), 5 deletions(-) diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index 70128b4c62..fafd6668bf 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -986,7 +986,55 @@ namespace MIPSComp { // d[N] = int(S[N] * mult) // Note: saturates on overflow. - DISABLE; + VectorSize sz = GetVecSize(op); + int n = GetNumVectorElements(sz); + + int imm = (op >> 16) & 0x1f; + + u8 sregs[4], dregs[4]; + GetVectorRegsPrefixS(sregs, sz, _VS); + GetVectorRegsPrefixD(dregs, sz, _VD); + + // Same values as FCR31. + uint8_t rmode = (op >> 21) & 3; + if (((op >> 21) & 0x1C) != 0x10) + INVALIDOP; + + u8 tempregs[4]; + for (int i = 0; i < n; ++i) { + if (!IsOverlapSafe(dregs[i], n, sregs)) { + tempregs[i] = IRVTEMP_PFX_T + i; // Need IRVTEMP_0 for the scaling factor + } else { + tempregs[i] = dregs[i]; + } + } + if (imm != 0) { + for (int i = 0; i < n; i++) + ir.Write(IROp::FCvtScaledWS, dregs[i], sregs[i], (uint8_t)(imm | (rmode << 6))); + } else { + for (int i = 0; i < n; i++) { + switch (rmode) { + case 0: // vf2in + ir.Write(IROp::FRound, dregs[i], sregs[i]); + break; + + case 1: // vf2iz + ir.Write(IROp::FTrunc, dregs[i], sregs[i]); + break; + + case 2: // vf2iu + ir.Write(IROp::FCeil, dregs[i], sregs[i]); + break; + + case 3: // vf2id + ir.Write(IROp::FFloor, dregs[i], sregs[i]); + break; + + default: + INVALIDOP; + } + } + } } void IRFrontend::Comp_Mftv(MIPSOpcode op) { diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 116908c656..ed7bd403b0 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -105,6 +105,7 @@ static const IRMeta irMeta[] = { { IROp::FFloor, "FFloor", "FF" }, { IROp::FCvtWS, "FCvtWS", "FF" }, { IROp::FCvtSW, "FCvtSW", "FF" }, + { IROp::FCvtScaledWS, "FCvtScaledWS", "FFI" }, { IROp::FCmp, "FCmp", "mFF" }, { IROp::FSat0_1, "FSat(0 - 1)", "FF" }, { IROp::FSatMinus1_1, "FSat(-1 - 1)", "FF" }, diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index d4eb47f49a..2e5a1afce8 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -127,6 +127,7 @@ enum class IROp : u8 { FCvtWS, FCvtSW, + FCvtScaledWS, FMovFromGPR, FMovToGPR, @@ -304,9 +305,6 @@ enum : IRReg { IRVTEMP_PFX_D = 232 - 32, IRVTEMP_0 = 236 - 32, - // 16 float temps for vector S and T prefixes and things like that. - // IRVTEMP_0 = 208 - 64, // -64 to be relative to v[0] - // Hacky way to get to other state IRREG_VFPU_CTRL_BASE = 208, IRREG_VFPU_CC = 211, diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 561adf6f25..22b012787f 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -834,7 +834,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL; break; } else { - mips->fs[inst->dest] = (int)floorf(value + 0.5f); + mips->fs[inst->dest] = (int)round_ieee_754(value); } break; } @@ -931,6 +931,32 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { } break; //cvt.w.s } + case IROp::FCvtScaledWS: + { + float src = mips->f[inst->src1]; + if (my_isnan(src)) { + // TODO: True for negatives too? + mips->fs[inst->dest] = 2147483647L; + break; + } + + float mult = (float)(1UL << (inst->src2 & 0x1F)); + double sv = src * mult; // (float)0x7fffffff == (float)0x80000000 + // Cap/floor it to 0x7fffffff / 0x80000000 + if (sv > (double)0x7fffffff) { + mips->fs[inst->dest] = 0x7fffffff; + } else if (sv <= (double)(int)0x80000000) { + mips->fs[inst->dest] = 0x80000000; + } else { + switch (inst->src2 >> 6) { + case 0: mips->fs[inst->dest] = (int)round_ieee_754(sv); break; + case 1: mips->fs[inst->dest] = src >= 0 ? (int)floor(sv) : (int)ceil(sv); break; + case 2: mips->fs[inst->dest] = (int)ceil(sv); break; + case 3: mips->fs[inst->dest] = (int)floor(sv); break; + } + } + break; + } case IROp::ZeroFpCond: mips->fpcond = 0; diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 52ea66c5d4..247b3b6865 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -667,6 +667,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::FCeil: case IROp::FFloor: case IROp::FCvtSW: + case IROp::FCvtScaledWS: case IROp::FSin: case IROp::FCos: case IROp::FSqrt: diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index 84c7faa832..38d23f900c 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -190,6 +190,7 @@ void RiscVJit::CompIR_FCvt(IRInst inst) { switch (inst.op) { case IROp::FCvtWS: + case IROp::FCvtScaledWS: case IROp::FCvtSW: CompIR_Generic(inst); break; diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index b94369c4ca..6edea8d25e 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -265,6 +265,7 @@ void RiscVJit::CompileIRInst(IRInst inst) { case IROp::FCvtWS: case IROp::FCvtSW: + case IROp::FCvtScaledWS: CompIR_FCvt(inst); break; From e228748449c88b78e4b362aa9b1493562efa02e0 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 18:15:49 -0700 Subject: [PATCH 126/167] irjit: Add FCvtScaledSW to safely scale vi2f. --- Core/MIPS/IR/IRCompVFPU.cpp | 42 ++++++-------------------------- Core/MIPS/IR/IRInst.cpp | 1 + Core/MIPS/IR/IRInst.h | 1 + Core/MIPS/IR/IRInterpreter.cpp | 3 +++ Core/MIPS/IR/IRPassSimplify.cpp | 1 + Core/MIPS/RiscV/RiscVCompFPU.cpp | 1 + Core/MIPS/RiscV/RiscVJit.cpp | 1 + 7 files changed, 15 insertions(+), 35 deletions(-) diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index fafd6668bf..1c767d8e3d 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -929,37 +929,17 @@ namespace MIPSComp { VectorSize sz = GetVecSize(op); int n = GetNumVectorElements(sz); - int imm = (op >> 16) & 0x1f; - const float mult = 1.0f / (float)(1UL << imm); + uint8_t imm = (op >> 16) & 0x1f; u8 sregs[4], dregs[4]; GetVectorRegsPrefixS(sregs, sz, _VS); GetVectorRegsPrefixD(dregs, sz, _VD); - u8 tempregs[4]; - for (int i = 0; i < n; ++i) { - if (!IsOverlapSafe(dregs[i], n, sregs)) { - tempregs[i] = IRVTEMP_PFX_T + i; // Need IRVTEMP_0 for the scaling factor - } else { - tempregs[i] = dregs[i]; - } - } - if (mult != 1.0f) - ir.Write(IROp::SetConstF, IRVTEMP_0, ir.AddConstantFloat(mult)); - // TODO: Use the SCVTF with builtin scaling where possible. for (int i = 0; i < n; i++) { - ir.Write(IROp::FCvtSW, tempregs[i], sregs[i]); - } - if (mult != 1.0f) { - for (int i = 0; i < n; i++) { - ir.Write(IROp::FMul, tempregs[i], tempregs[i], IRVTEMP_0); - } - } - - for (int i = 0; i < n; ++i) { - if (dregs[i] != tempregs[i]) { - ir.Write(IROp::FMov, dregs[i], tempregs[i]); - } + if (imm == 0) + ir.Write(IROp::FCvtSW, dregs[i], sregs[i]); + else + ir.Write(IROp::FCvtScaledSW, dregs[i], sregs[i], imm); } ApplyPrefixD(dregs, sz); } @@ -989,7 +969,7 @@ namespace MIPSComp { VectorSize sz = GetVecSize(op); int n = GetNumVectorElements(sz); - int imm = (op >> 16) & 0x1f; + uint8_t imm = (op >> 16) & 0x1f; u8 sregs[4], dregs[4]; GetVectorRegsPrefixS(sregs, sz, _VS); @@ -1000,17 +980,9 @@ namespace MIPSComp { if (((op >> 21) & 0x1C) != 0x10) INVALIDOP; - u8 tempregs[4]; - for (int i = 0; i < n; ++i) { - if (!IsOverlapSafe(dregs[i], n, sregs)) { - tempregs[i] = IRVTEMP_PFX_T + i; // Need IRVTEMP_0 for the scaling factor - } else { - tempregs[i] = dregs[i]; - } - } if (imm != 0) { for (int i = 0; i < n; i++) - ir.Write(IROp::FCvtScaledWS, dregs[i], sregs[i], (uint8_t)(imm | (rmode << 6))); + ir.Write(IROp::FCvtScaledWS, dregs[i], sregs[i], imm | (rmode << 6)); } else { for (int i = 0; i < n; i++) { switch (rmode) { diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index ed7bd403b0..6d7d46e888 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -106,6 +106,7 @@ static const IRMeta irMeta[] = { { IROp::FCvtWS, "FCvtWS", "FF" }, { IROp::FCvtSW, "FCvtSW", "FF" }, { IROp::FCvtScaledWS, "FCvtScaledWS", "FFI" }, + { IROp::FCvtScaledSW, "FCvtScaledSW", "FFI" }, { IROp::FCmp, "FCmp", "mFF" }, { IROp::FSat0_1, "FSat(0 - 1)", "FF" }, { IROp::FSatMinus1_1, "FSat(-1 - 1)", "FF" }, diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 2e5a1afce8..2ffc5c79f2 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -128,6 +128,7 @@ enum class IROp : u8 { FCvtWS, FCvtSW, FCvtScaledWS, + FCvtScaledSW, FMovFromGPR, FMovToGPR, diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 22b012787f..c15d61f518 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -931,6 +931,9 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { } break; //cvt.w.s } + case IROp::FCvtScaledSW: + mips->f[inst->dest] = (float)mips->fs[inst->src1] * (1.0f / (1UL << (inst->src2 & 0x1F))); + break; case IROp::FCvtScaledWS: { float src = mips->f[inst->src1]; diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 247b3b6865..7f218d35c7 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -668,6 +668,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::FFloor: case IROp::FCvtSW: case IROp::FCvtScaledWS: + case IROp::FCvtScaledSW: case IROp::FSin: case IROp::FCos: case IROp::FSqrt: diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index 38d23f900c..fd3807d907 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -192,6 +192,7 @@ void RiscVJit::CompIR_FCvt(IRInst inst) { case IROp::FCvtWS: case IROp::FCvtScaledWS: case IROp::FCvtSW: + case IROp::FCvtScaledSW: CompIR_Generic(inst); break; diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 6edea8d25e..73962198c2 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -266,6 +266,7 @@ void RiscVJit::CompileIRInst(IRInst inst) { case IROp::FCvtWS: case IROp::FCvtSW: case IROp::FCvtScaledWS: + case IROp::FCvtScaledSW: CompIR_FCvt(inst); break; From 9a8ac1fe086dde4a6d8f3cdf0f2f4c10caedf30f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 18:49:45 -0700 Subject: [PATCH 127/167] x86jit: Implement ll/sc. The point here is that breakpoints now work for ll and sc. --- Core/MIPS/x86/CompLoadStore.cpp | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/x86/CompLoadStore.cpp b/Core/MIPS/x86/CompLoadStore.cpp index 9ef4b0b206..603e3e367a 100644 --- a/Core/MIPS/x86/CompLoadStore.cpp +++ b/Core/MIPS/x86/CompLoadStore.cpp @@ -44,6 +44,7 @@ // #define CONDITIONAL_DISABLE(flag) { Comp_Generic(op); return; } #define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; } #define DISABLE { Comp_Generic(op); return; } +#define INVALIDOP { Comp_Generic(op); return; } namespace MIPSComp { using namespace Gen; @@ -408,7 +409,37 @@ namespace MIPSComp { void Jit::Comp_StoreSync(MIPSOpcode op) { CONDITIONAL_DISABLE(LSU); - DISABLE; + int offset = _IMM16; + MIPSGPReg rt = _RT; + MIPSGPReg rs = _RS; + // Note: still does something even if loading to zero. + + CheckMemoryBreakpoint(0, rs, offset); + + FixupBranch skipStore; + FixupBranch finish; + switch (op >> 26) { + case 48: // ll + CompITypeMemRead(op, 32, &XEmitter::MOVZX, safeMemFuncs.readU32); + MOV(8, MDisp(X64JitConstants::CTXREG, -128 + offsetof(MIPSState, llBit)), Imm8(1)); + break; + + case 56: // sc + CMP(8, MDisp(X64JitConstants::CTXREG, -128 + offsetof(MIPSState, llBit)), Imm8(1)); + skipStore = J_CC(CC_NE); + + CompITypeMemWrite(op, 32, safeMemFuncs.writeU32); + MOV(32, gpr.R(rt), Imm32(1)); + finish = J(); + + SetJumpTarget(skipStore); + MOV(32, gpr.R(rt), Imm32(0)); + SetJumpTarget(finish); + break; + + default: + INVALIDOP; + } } void Jit::Comp_Cache(MIPSOpcode op) { From b6d2e64acaef3c9761be306886dcfb33a0cca6b6 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 18:50:09 -0700 Subject: [PATCH 128/167] Debugger: Fix disasm of ll/sc. --- Core/MIPS/MIPSTables.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/MIPSTables.cpp b/Core/MIPS/MIPSTables.cpp index f012b0eab4..28ff363da9 100644 --- a/Core/MIPS/MIPSTables.cpp +++ b/Core/MIPS/MIPSTables.cpp @@ -150,7 +150,7 @@ static const MIPSInstruction tableImmediate[64] = // xxxxxx ..... ..... ........ INSTR("swr", JITFUNC(Comp_ITypeMem), Dis_ITypeMem, Int_ITypeMem, IN_IMM16|IN_RS_ADDR|IN_RT|OUT_MEM|MEMTYPE_WORD), INSTR("cache", JITFUNC(Comp_Cache), Dis_Cache, Int_Cache, IN_MEM|IN_IMM16|IN_RS_ADDR), //48 - INSTR("ll", JITFUNC(Comp_StoreSync), Dis_Generic, Int_StoreSync, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_RT|OUT_OTHER|MEMTYPE_WORD), + INSTR("ll", JITFUNC(Comp_StoreSync), Dis_ITypeMem, Int_StoreSync, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_RT|OUT_OTHER|MEMTYPE_WORD), INSTR("lwc1", JITFUNC(Comp_FPULS), Dis_FPULS, Int_FPULS, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_FT|MEMTYPE_FLOAT|IS_FPU), INSTR("lv.s", JITFUNC(Comp_SV), Dis_SV, Int_SV, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_OTHER|IS_VFPU|VFPU_NO_PREFIX|MEMTYPE_FLOAT), INVALID, @@ -159,7 +159,7 @@ static const MIPSInstruction tableImmediate[64] = // xxxxxx ..... ..... ........ INSTR("lv.q", JITFUNC(Comp_SVQ), Dis_SVQ, Int_SVQ, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_OTHER|IS_VFPU|VFPU_NO_PREFIX|MEMTYPE_VQUAD), //copU ENCODING(VFPU5), //56 - INSTR("sc", JITFUNC(Comp_StoreSync), Dis_Generic, Int_StoreSync, IN_IMM16|IN_RS_ADDR|IN_OTHER|IN_RT|OUT_RT|OUT_MEM|MEMTYPE_WORD), + INSTR("sc", JITFUNC(Comp_StoreSync), Dis_ITypeMem, Int_StoreSync, IN_IMM16|IN_RS_ADDR|IN_OTHER|IN_RT|OUT_RT|OUT_MEM|MEMTYPE_WORD), INSTR("swc1", JITFUNC(Comp_FPULS), Dis_FPULS, Int_FPULS, IN_IMM16|IN_RS_ADDR|IN_FT|OUT_MEM|MEMTYPE_FLOAT|IS_FPU), //copU INSTR("sv.s", JITFUNC(Comp_SV), Dis_SV, Int_SV, IN_IMM16|IN_RS_ADDR|IN_OTHER|OUT_MEM|IS_VFPU|VFPU_NO_PREFIX|MEMTYPE_FLOAT), INVALID, From 8d60c10a6441f5f0978f6339f3284a48d33dc09e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 27 Jul 2023 22:23:06 -0700 Subject: [PATCH 129/167] riscv: Use jit address offsets directly. We'll have IR able to use block number or target offset. --- Core/MIPS/IR/IRJit.cpp | 58 +++++++++++++++++++++++++----------- Core/MIPS/IR/IRJit.h | 26 +++++++++++----- Core/MIPS/RiscV/RiscVAsm.cpp | 34 ++++----------------- Core/MIPS/RiscV/RiscVJit.cpp | 50 +++++++++---------------------- Core/MIPS/RiscV/RiscVJit.h | 5 +--- 5 files changed, 78 insertions(+), 95 deletions(-) diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index ffab92975c..1253ca5061 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -85,7 +85,8 @@ void IRJit::Compile(u32 em_address) { if (block_num != -1) { IRBlock *b = blocks_.GetBlock(block_num); // Okay, let's link and finalize the block now. - b->Finalize(block_num); + int cookie = b->GetTargetOffset() < 0 ? block_num : b->GetTargetOffset(); + b->Finalize(cookie); if (b->IsValid()) { // Success, we're done. return; @@ -128,13 +129,13 @@ bool IRJit::CompileBlock(u32 em_address, std::vector &instructions, u32 b->SetOriginalSize(mipsBytes); if (preload) { // Hash, then only update page stats, don't link yet. - b->UpdateHash(); - blocks_.FinalizeBlock(block_num, true); - } else { - // Overwrites the first instruction, and also updates stats. // TODO: Should we always hash? Then we can reuse blocks. - blocks_.FinalizeBlock(block_num); + b->UpdateHash(); } + if (!CompileTargetBlock(b, block_num, preload)) + return false; + // Overwrites the first instruction, and also updates stats. + blocks_.FinalizeBlock(block_num, preload); return true; } @@ -264,7 +265,8 @@ void IRJit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) { void IRBlockCache::Clear() { for (int i = 0; i < (int)blocks_.size(); ++i) { - blocks_[i].Destroy(i); + int cookie = blocks_[i].GetTargetOffset() < 0 ? i : blocks_[i].GetTargetOffset(); + blocks_[i].Destroy(cookie); } blocks_.clear(); byPage_.clear(); @@ -283,7 +285,8 @@ void IRBlockCache::InvalidateICache(u32 address, u32 length) { for (int i : blocksInPage) { if (blocks_[i].OverlapsRange(address, length)) { // Not removing from the page, hopefully doesn't build up with small recompiles. - blocks_[i].Destroy(i); + int cookie = blocks_[i].GetTargetOffset() < 0 ? i : blocks_[i].GetTargetOffset(); + blocks_[i].Destroy(cookie); } } } @@ -291,7 +294,8 @@ void IRBlockCache::InvalidateICache(u32 address, u32 length) { void IRBlockCache::FinalizeBlock(int i, bool preload) { if (!preload) { - blocks_[i].Finalize(i); + int cookie = blocks_[i].GetTargetOffset() < 0 ? i : blocks_[i].GetTargetOffset(); + blocks_[i].Finalize(cookie); } u32 startAddr, size; @@ -331,13 +335,30 @@ int IRBlockCache::FindPreloadBlock(u32 em_address) { return -1; } +int IRBlockCache::FindByCookie(int cookie) { + if (blocks_.empty()) + return -1; + // TODO: Maybe a flag to determine target offset mode? + if (blocks_[0].GetTargetOffset() < 0) + return cookie; + + for (int i = 0; i < GetNumBlocks(); ++i) { + int offset = blocks_[i].GetTargetOffset(); + if (offset == cookie) + return i; + } + + return -1; +} + std::vector IRBlockCache::SaveAndClearEmuHackOps() { std::vector result; result.resize(blocks_.size()); for (int number = 0; number < (int)blocks_.size(); ++number) { IRBlock &b = blocks_[number]; - if (b.IsValid() && b.RestoreOriginalFirstOp(number)) { + int cookie = b.GetTargetOffset() < 0 ? number : b.GetTargetOffset(); + if (b.IsValid() && b.RestoreOriginalFirstOp(cookie)) { result[number] = number; } else { result[number] = 0; @@ -357,7 +378,8 @@ void IRBlockCache::RestoreSavedEmuHackOps(std::vector saved) { IRBlock &b = blocks_[number]; // Only if we restored it, write it back. if (b.IsValid() && saved[number] != 0 && b.HasOriginalFirstOp()) { - b.Finalize(number); + int cookie = b.GetTargetOffset() < 0 ? number : b.GetTargetOffset(); + b.Finalize(cookie); } } } @@ -441,8 +463,8 @@ bool IRBlock::HasOriginalFirstOp() const { return Memory::ReadUnchecked_U32(origAddr_) == origFirstOpcode_.encoding; } -bool IRBlock::RestoreOriginalFirstOp(int number) { - const u32 emuhack = MIPS_EMUHACK_OPCODE | number; +bool IRBlock::RestoreOriginalFirstOp(int cookie) { + const u32 emuhack = MIPS_EMUHACK_OPCODE | cookie; if (Memory::ReadUnchecked_U32(origAddr_) == emuhack) { Memory::Write_Opcode_JIT(origAddr_, origFirstOpcode_); return true; @@ -450,19 +472,19 @@ bool IRBlock::RestoreOriginalFirstOp(int number) { return false; } -void IRBlock::Finalize(int number) { +void IRBlock::Finalize(int cookie) { // Check it wasn't invalidated, in case this is after preload. // TODO: Allow reusing blocks when the code matches hash_ again, instead. if (origAddr_) { origFirstOpcode_ = Memory::Read_Opcode_JIT(origAddr_); - MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | number); + MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | cookie); Memory::Write_Opcode_JIT(origAddr_, opcode); } } -void IRBlock::Destroy(int number) { +void IRBlock::Destroy(int cookie) { if (origAddr_) { - MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | number); + MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | cookie); if (Memory::ReadUnchecked_U32(origAddr_) == opcode.encoding) Memory::Write_Opcode_JIT(origAddr_, origFirstOpcode_); @@ -496,7 +518,7 @@ bool IRBlock::OverlapsRange(u32 addr, u32 size) const { } MIPSOpcode IRJit::GetOriginalOp(MIPSOpcode op) { - IRBlock *b = blocks_.GetBlock(op.encoding & 0xFFFFFF); + IRBlock *b = blocks_.GetBlock(blocks_.FindByCookie(op.encoding & 0xFFFFFF)); if (b) { return b->GetOriginalFirstOp(); } diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h index 756a584ddf..35387e7e44 100644 --- a/Core/MIPS/IR/IRJit.h +++ b/Core/MIPS/IR/IRJit.h @@ -38,15 +38,16 @@ namespace MIPSComp { // TODO : Use arena allocators. For now let's just malloc. class IRBlock { public: - IRBlock() : instr_(nullptr), numInstructions_(0), origAddr_(0), origSize_(0) {} - IRBlock(u32 emAddr) : instr_(nullptr), numInstructions_(0), origAddr_(emAddr), origSize_(0) {} + IRBlock() {} + IRBlock(u32 emAddr) : origAddr_(emAddr) {} IRBlock(IRBlock &&b) { instr_ = b.instr_; - numInstructions_ = b.numInstructions_; + hash_ = b.hash_; origAddr_ = b.origAddr_; origSize_ = b.origSize_; origFirstOpcode_ = b.origFirstOpcode_; - hash_ = b.hash_; + targetOffset_ = b.targetOffset_; + numInstructions_ = b.numInstructions_; b.instr_ = nullptr; } @@ -71,6 +72,12 @@ public: void SetOriginalSize(u32 size) { origSize_ = size; } + void SetTargetOffset(int offset) { + targetOffset_ = offset; + } + int GetTargetOffset() const { + return targetOffset_; + } void UpdateHash() { hash_ = CalculateHash(); } @@ -90,12 +97,13 @@ public: private: u64 CalculateHash() const; - IRInst *instr_; - u16 numInstructions_; - u32 origAddr_; - u32 origSize_; + IRInst *instr_ = nullptr; u64 hash_ = 0; + u32 origAddr_ = 0; + u32 origSize_ = 0; MIPSOpcode origFirstOpcode_ = MIPSOpcode(0x68FFFFFF); + int targetOffset_ = -1; + u16 numInstructions_ = 0; }; class IRBlockCache : public JitBlockCacheDebugInterface { @@ -118,6 +126,7 @@ public: } int FindPreloadBlock(u32 em_address); + int FindByCookie(int cookie); std::vector SaveAndClearEmuHackOps(); void RestoreSavedEmuHackOps(std::vector saved); @@ -172,6 +181,7 @@ public: protected: virtual bool CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload); + virtual bool CompileTargetBlock(IRBlock *block, int block_num, bool preload) { return true; } JitOptions jo; diff --git a/Core/MIPS/RiscV/RiscVAsm.cpp b/Core/MIPS/RiscV/RiscVAsm.cpp index 67dead2e1e..54dda12daf 100644 --- a/Core/MIPS/RiscV/RiscVAsm.cpp +++ b/Core/MIPS/RiscV/RiscVAsm.cpp @@ -131,7 +131,7 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { // Fixed registers, these are always kept when in Jit context. LI(MEMBASEREG, Memory::base, SCRATCH1); LI(CTXREG, mips_, SCRATCH1); - LI(JITBASEREG, blockStartAddrs_, SCRATCH1); + LI(JITBASEREG, GetBasePtr(), SCRATCH1); LoadStaticRegisters(); MovFromPC(SCRATCH1); @@ -183,35 +183,11 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { // We're in other words comparing to the top 8 bits of MIPS_EMUHACK_OPCODE by subtracting. ADDI(SCRATCH2, SCRATCH2, -(MIPS_EMUHACK_OPCODE >> 24)); FixupBranch needsCompile = BNE(SCRATCH2, R_ZERO); - // Use a wall to mask by 0x00FFFFFF and extract the block number. + // Use a wall to mask by 0x00FFFFFF and extract the block jit offset. SLLI(SCRATCH1, SCRATCH1, XLEN - 24); - // But actually, we want * 8, so skip shifting back just a bit. - _assert_msg_(sizeof(blockStartAddrs_[0]) == 8, "RiscVAsm currently assumes pointers are 64-bit"); - SRLI(SCRATCH1, SCRATCH1, XLEN - 24 - 3); - if (enableDebug) { - // Let's do some extra validation of the block number in debug mode for testing. - - LI(SCRATCH2, MAX_ALLOWED_JIT_BLOCKS * 8); - FixupBranch highBlockNum = BGEU(SCRATCH1, SCRATCH2); - ADD(SCRATCH1, JITBASEREG, SCRATCH1); - // TODO: Consider replacing the block nums after all, just trying to use IR block cache. - LD(SCRATCH1, SCRATCH1, 0); - LI(SCRATCH2, 2); - FixupBranch invalidBlockNum = BEQ(SCRATCH1, R_ZERO); - JR(SCRATCH1); - - SetJumpTarget(highBlockNum); - LI(SCRATCH2, 1); - SetJumpTarget(invalidBlockNum); - - MV(X10, SCRATCH2); - QuickCallFunction(&ShowBlockError); - } else { - ADD(SCRATCH1, JITBASEREG, SCRATCH1); - // TODO: Consider replacing the block nums after all, just trying to use IR block cache. - LD(SCRATCH1, SCRATCH1, 0); - JR(SCRATCH1); - } + SRLI(SCRATCH1, SCRATCH1, XLEN - 24); + ADD(SCRATCH1, JITBASEREG, SCRATCH1); + JR(SCRATCH1); SetJumpTarget(needsCompile); // No block found, let's jit. We don't need to save static regs, they're all callee saved. diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index b94369c4ca..5ea5bff04a 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -32,13 +32,11 @@ RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo) jo.enablePointerify = false; } + // Since we store the offset, this is as big as it can be. + // We could shift off one bit to double it, would need to change RiscVAsm. AllocCodeSpace(1024 * 1024 * 16); SetAutoCompress(true); - // TODO: Consider replacing block num method form IRJit - this is 2MB. - blockStartAddrs_ = new const u8 *[MAX_ALLOWED_JIT_BLOCKS]; - memset(blockStartAddrs_, 0, sizeof(blockStartAddrs_[0]) * MAX_ALLOWED_JIT_BLOCKS); - gpr.Init(this); fpr.Init(this); @@ -46,7 +44,6 @@ RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo) } RiscVJit::~RiscVJit() { - delete [] blockStartAddrs_; } void RiscVJit::RunLoopUntil(u64 globalticks) { @@ -54,34 +51,20 @@ void RiscVJit::RunLoopUntil(u64 globalticks) { ((void (*)())enterDispatcher_)(); } -bool RiscVJit::CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload) { - // Check that we're not full (we allow less blocks than IR itself.) - if (blocks_.GetNumBlocks() >= MAX_ALLOWED_JIT_BLOCKS - 1) +bool RiscVJit::CompileTargetBlock(IRBlock *block, int block_num, bool preload) { + if (GetSpaceLeft() < 0x800) return false; - if (!IRJit::CompileBlock(em_address, instructions, mipsBytes, preload)) - return false; + // Don't worry, the codespace isn't large enough to overflow offsets. + block->SetTargetOffset((int)GetOffset(GetCodePointer())); // TODO: Block linking, checked entries and such. - int block_num; - if (preload) { - block_num = blocks_.GetBlockNumberFromStartAddress(em_address); - } else { - u32 first_inst = Memory::ReadUnchecked_U32(em_address); - _assert_msg_(MIPS_IS_RUNBLOCK(first_inst), "Should've written an emuhack"); - - block_num = first_inst & MIPS_EMUHACK_VALUE_MASK; - } - - _assert_msg_(block_num >= 0 && block_num < MAX_ALLOWED_JIT_BLOCKS, "Bad block num"); - _assert_msg_(blockStartAddrs_[block_num] == nullptr, "Block %d reused before clear", block_num); - blockStartAddrs_[block_num] = GetCodePointer(); - gpr.Start(); fpr.Start(); - for (const IRInst &inst : instructions) { + for (int i = 0; i < block->GetNumInstructions(); ++i) { + const IRInst &inst = block->GetInstructions()[i]; CompileIRInst(inst); if (jo.Disabled(JitDisable::REGALLOC_GPR)) { @@ -442,17 +425,14 @@ bool RiscVJit::DescribeCodePtr(const u8 *ptr, std::string &name) { } else if (!IsInSpace(ptr)) { return false; } else { - uintptr_t uptr = (uintptr_t)ptr; + int offset = (int)GetOffset(ptr); int block_num = -1; - for (int i = 0; i < MAX_ALLOWED_JIT_BLOCKS; ++i) { - uintptr_t blockptr = (uintptr_t)blockStartAddrs_[i]; - // Out of allocated blocks. - if (uptr == 0) - break; - - if (uptr >= blockptr) + for (int i = 0; i < blocks_.GetNumBlocks(); ++i) { + const auto &b = blocks_.GetBlock(i); + // We allocate linearly. + if (b->GetTargetOffset() <= offset) block_num = i; - if (uptr < blockptr) + if (b->GetTargetOffset() > offset) break; } @@ -494,8 +474,6 @@ void RiscVJit::ClearCache() { ClearCodeSpace(jitStartOffset_); FlushIcacheSection(region + jitStartOffset_, region + region_size - jitStartOffset_); - - memset(blockStartAddrs_, 0, sizeof(blockStartAddrs_[0]) * MAX_ALLOWED_JIT_BLOCKS); } void RiscVJit::RestoreRoundingMode(bool force) { diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index 02c90c735d..6c3958dcf7 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -46,7 +46,7 @@ public: // TODO: GetBlockCacheDebugInterface, block linking? protected: - bool CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload) override; + bool CompileTargetBlock(IRBlock *block, int block_num, bool preload) override; void CompileIRInst(IRInst inst); @@ -115,8 +115,6 @@ private: RiscVRegCache gpr; RiscVRegCacheFPU fpr; - static constexpr int MAX_ALLOWED_JIT_BLOCKS = 262144; - const u8 *enterDispatcher_ = nullptr; const u8 *outerLoop_ = nullptr; @@ -134,7 +132,6 @@ private: const u8 *crashHandler_ = nullptr; int jitStartOffset_ = 0; - const u8 **blockStartAddrs_ = nullptr; }; } // namespace MIPSComp From f65b6fdb20f959a4b126c8825a8ec289c3f05c26 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 27 Jul 2023 22:23:47 -0700 Subject: [PATCH 130/167] riscv: Remove incomplete block check. It shouldn't be necessary and bad things would happen anyway if it did. --- Core/MIPS/RiscV/RiscVAsm.cpp | 11 +++++------ Core/MIPS/RiscV/RiscVJit.cpp | 14 +++++++++++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVAsm.cpp b/Core/MIPS/RiscV/RiscVAsm.cpp index 54dda12daf..87acfa8290 100644 --- a/Core/MIPS/RiscV/RiscVAsm.cpp +++ b/Core/MIPS/RiscV/RiscVAsm.cpp @@ -112,7 +112,7 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { static constexpr RiscVReg regs_to_save[]{ R_RA, X8, X9, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27 }; // TODO: Maybe we shouldn't regalloc all of these? Is it worth it? static constexpr RiscVReg regs_to_save_fp[]{ F8, F9, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27 }; - int saveSize = 8 * (int)(ARRAY_SIZE(regs_to_save) + ARRAY_SIZE(regs_to_save_fp)); + int saveSize = (XLEN / 8) * (int)(ARRAY_SIZE(regs_to_save) + ARRAY_SIZE(regs_to_save_fp)); if (saveSize & 0xF) saveSize += 8; _assert_msg_((saveSize & 0xF) == 0, "Stack must be kept aligned"); @@ -120,11 +120,11 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { ADDI(R_SP, R_SP, -saveSize); for (RiscVReg r : regs_to_save) { SD(r, R_SP, saveOffset); - saveOffset += 8; + saveOffset += XLEN / 8; } for (RiscVReg r : regs_to_save_fp) { FS(64, r, R_SP, saveOffset); - saveOffset += 8; + saveOffset += XLEN / 8; } _assert_(saveOffset <= saveSize); @@ -214,17 +214,16 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { saveOffset = 0; for (RiscVReg r : regs_to_save) { LD(r, R_SP, saveOffset); - saveOffset += 8; + saveOffset += XLEN / 8; } for (RiscVReg r : regs_to_save_fp) { FL(64, r, R_SP, saveOffset); - saveOffset += 8; + saveOffset += XLEN / 8; } ADDI(R_SP, R_SP, saveSize); RET(); - // TODO crashHandler_ = GetCodePtr(); LI(SCRATCH1, &coreState, SCRATCH2); LI(SCRATCH2, CORE_RUNTIME_ERROR); diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 5ea5bff04a..4ef111f7ef 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -26,6 +26,8 @@ namespace MIPSComp { using namespace RiscVGen; using namespace RiscVJitConstants; +static constexpr bool enableDebug = false; + RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo), fpr(mipsState, &jo) { // Automatically disable incompatible options. if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) { @@ -51,6 +53,10 @@ void RiscVJit::RunLoopUntil(u64 globalticks) { ((void (*)())enterDispatcher_)(); } +static void NoBlockExits() { + _assert_msg_(false, "Never exited block, invalid IR?"); +} + bool RiscVJit::CompileTargetBlock(IRBlock *block, int block_num, bool preload) { if (GetSpaceLeft() < 0x800) return false; @@ -80,9 +86,11 @@ bool RiscVJit::CompileTargetBlock(IRBlock *block, int block_num, bool preload) { } } - // Note: a properly constructed block should never get here. - // TODO: Need to do more than just this? Call a func to set an exception? - QuickJ(R_RA, crashHandler_); + // We should've written an exit above. If we didn't, bad things will happen. + if (enableDebug) { + QuickCallFunction(&NoBlockExits); + QuickJ(R_RA, crashHandler_); + } FlushIcache(); From e2765db4dc72ee475350531eec2334129e421891 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 07:42:36 -0700 Subject: [PATCH 131/167] riscv: Implement division. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 45 +++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index 61c0da3a70..aa3197dbf1 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -640,10 +640,53 @@ void RiscVJit::CompIR_Mult(IRInst inst) { void RiscVJit::CompIR_Div(IRInst inst) { CONDITIONAL_DISABLE; + RiscVReg numReg, denomReg; switch (inst.op) { case IROp::Div: + gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32); + // We have to do this because of the divide by zero and overflow checks below. + NormalizeSrc12(inst, &numReg, &denomReg, SCRATCH1, SCRATCH2, true); + DIVW(gpr.R(IRREG_LO), numReg, denomReg); + REMW(gpr.R(IRREG_HI), numReg, denomReg); + + // Now some tweaks for divide by zero and overflow. + { + // Start with divide by zero, remainder is fine. + FixupBranch skipNonZero = BNE(denomReg, R_ZERO); + FixupBranch keepNegOne = BGE(numReg, R_ZERO); + LI(gpr.R(IRREG_LO), 1); + SetJumpTarget(keepNegOne); + SetJumpTarget(skipNonZero); + + // For overflow, RISC-V sets LO right, but remainder to zero. + // Cheating a bit by using R_RA as a temp... + LI(R_RA, (int32_t)0x80000000); + FixupBranch notMostNegative = BNE(numReg, R_RA); + LI(R_RA, -1); + FixupBranch notNegativeOne = BNE(denomReg, R_RA); + LI(gpr.R(IRREG_HI), -1); + SetJumpTarget(notNegativeOne); + SetJumpTarget(notMostNegative); + } + break; + case IROp::DivU: - CompIR_Generic(inst); + gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32); + // We have to do this because of the divide by zero check below. + NormalizeSrc12(inst, &numReg, &denomReg, SCRATCH1, SCRATCH2, true); + DIVUW(gpr.R(IRREG_LO), numReg, denomReg); + REMUW(gpr.R(IRREG_HI), numReg, denomReg); + + // On divide by zero, everything is correct already except the 0xFFFF case. + { + FixupBranch skipNonZero = BNE(denomReg, R_ZERO); + // Luckily, we don't need SCRATCH2/denomReg anymore. + LI(SCRATCH2, 0xFFFF); + FixupBranch keepNegOne = BLTU(SCRATCH2, numReg); + MV(gpr.R(IRREG_LO), SCRATCH2); + SetJumpTarget(keepNegOne); + SetJumpTarget(skipNonZero); + } break; default: From 921bd2391c809f1e5a7433a9253a5fac0d206ce1 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 07:50:06 -0700 Subject: [PATCH 132/167] riscv: Implement vi2s. --- Core/MIPS/RiscV/RiscVCompVec.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/RiscV/RiscVCompVec.cpp b/Core/MIPS/RiscV/RiscVCompVec.cpp index 30193b950e..8140f2b856 100644 --- a/Core/MIPS/RiscV/RiscVCompVec.cpp +++ b/Core/MIPS/RiscV/RiscVCompVec.cpp @@ -241,10 +241,25 @@ void RiscVJit::CompIR_VecPack(IRInst inst) { case IROp::Vec4Pack31To8: case IROp::Vec4Pack32To8: case IROp::Vec2Pack31To16: - case IROp::Vec2Pack32To16: CompIR_Generic(inst); break; + case IROp::Vec2Pack32To16: + fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src1 + 1); + FMV(FMv::X, FMv::W, SCRATCH1, fpr.R(inst.src1)); + FMV(FMv::X, FMv::W, SCRATCH2, fpr.R(inst.src1 + 1)); + // Keep in mind, this was sign-extended, so we have to zero the upper. + SLLI(SCRATCH1, SCRATCH1, XLEN - 32); + // Now we just set (SCRATCH2 & 0xFFFF0000) | SCRATCH1. + SRLI(SCRATCH1, SCRATCH1, XLEN - 16); + // Use a wall to mask. We can ignore the upper 32 here. + SRLI(SCRATCH2, SCRATCH2, 16); + SLLI(SCRATCH2, SCRATCH2, 16); + OR(SCRATCH1, SCRATCH1, SCRATCH2); + // Okay, to the floating point register. + FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1); + break; + default: INVALIDOP; break; From 6b632a103daa9681259620a0939da9ddc92b5ed8 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 08:02:41 -0700 Subject: [PATCH 133/167] riscv: Implement FSin/similar. --- Core/MIPS/RiscV/RiscVCompFPU.cpp | 59 +++++++++++++++++++++++++++- Core/MIPS/RiscV/RiscVRegCache.h | 2 +- Core/MIPS/RiscV/RiscVRegCacheFPU.cpp | 21 ++++++++-- Core/MIPS/RiscV/RiscVRegCacheFPU.h | 4 +- 4 files changed, 79 insertions(+), 7 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index 84c7faa832..9d08930848 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -257,13 +257,70 @@ void RiscVJit::CompIR_RoundingMode(IRInst inst) { void RiscVJit::CompIR_FSpecial(IRInst inst) { CONDITIONAL_DISABLE; +#ifdef __riscv_float_abi_soft +#error Currently hard float is required. +#endif + + auto callFuncF_F = [&](float (*func)(float)){ + gpr.FlushBeforeCall(); + fpr.FlushBeforeCall(); + // It might be in a non-volatile register. + if (fpr.IsMapped(inst.src1)) { + FMV(32, F10, fpr.R(inst.src1)); + } else { + int offset = offsetof(MIPSState, f) + inst.src1 * 4; + FL(32, F10, CTXREG, offset); + } + QuickCallFunction(func); + + fpr.MapReg(inst.dest, MIPSMap::NOINIT); + // If it's already F10, we're done - MapReg doesn't actually overwrite the reg in that case. + if (fpr.R(inst.dest) != F10) { + FMV(32, fpr.R(inst.dest), F10); + } + }; + switch (inst.op) { case IROp::FSin: + callFuncF_F(&vfpu_sin); + break; + case IROp::FCos: + callFuncF_F(&vfpu_cos); + break; + case IROp::FRSqrt: + fpr.MapDirtyIn(inst.dest, inst.src1); + FSQRT(32, fpr.R(inst.dest), fpr.R(inst.src1)); + + // Ugh, we can't really avoid a temp here. Probably not worth a permanent one. + LI(SCRATCH1, 1.0f); + { + // TODO: Smarter allocation of a temp reg? + RiscVReg tempReg = fpr.R(inst.dest) == F31 ? F30 : F31; + fpr.FlushRiscVReg(tempReg); + FMV(FMv::W, FMv::X, tempReg, SCRATCH1); + FDIV(32, fpr.R(inst.dest), tempReg, fpr.R(inst.dest)); + } + break; + case IROp::FRecip: + fpr.MapDirtyIn(inst.dest, inst.src1); + LI(SCRATCH1, 1.0f); + if (inst.dest != inst.src1) { + // This is the easy case. + FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1); + FDIV(32, fpr.R(inst.dest), fpr.R(inst.dest), fpr.R(inst.src1)); + } else { + RiscVReg tempReg = fpr.R(inst.dest) == F31 ? F30 : F31; + fpr.FlushRiscVReg(tempReg); + FMV(FMv::W, FMv::X, tempReg, SCRATCH1); + FDIV(32, fpr.R(inst.dest), tempReg, fpr.R(inst.src1)); + } + break; + case IROp::FAsin: - CompIR_Generic(inst); + callFuncF_F(&vfpu_asin); break; default: diff --git a/Core/MIPS/RiscV/RiscVRegCache.h b/Core/MIPS/RiscV/RiscVRegCache.h index 72d680dc9b..10305f5e25 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.h +++ b/Core/MIPS/RiscV/RiscVRegCache.h @@ -140,6 +140,7 @@ public: void FlushBeforeCall(); void FlushAll(); void FlushR(IRRegIndex r); + void FlushRiscVReg(RiscVGen::RiscVReg r); void DiscardR(IRRegIndex r); RiscVGen::RiscVReg GetAndLockTempR(); @@ -163,7 +164,6 @@ private: RiscVGen::RiscVReg AllocateReg(); RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered); RiscVGen::RiscVReg RiscVRegForFlush(IRRegIndex r); - void FlushRiscVReg(RiscVGen::RiscVReg r); void SetRegImm(RiscVGen::RiscVReg reg, u64 imm); void AddMemBase(RiscVGen::RiscVReg reg); int GetMipsRegOffset(IRRegIndex r); diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp index 3277bcda89..7721477731 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp @@ -27,9 +27,6 @@ using namespace RiscVGen; using namespace RiscVJitConstants; -using namespace RiscVGen; -using namespace RiscVJitConstants; - RiscVRegCacheFPU::RiscVRegCacheFPU(MIPSState *mipsState, MIPSComp::JitOptions *jo) : mips_(mipsState), jo_(jo) {} @@ -279,6 +276,24 @@ RiscVReg RiscVRegCacheFPU::RiscVRegForFlush(IRRegIndex r) { } } +void RiscVRegCacheFPU::FlushBeforeCall() { + // Note: don't set this false at the end, since we don't flush everything. + if (!pendingFlush_) { + return; + } + + // These registers are not preserved by function calls. + for (int i = 0; i <= 7; ++i) { + FlushRiscVReg(RiscVReg(F0 + i)); + } + for (int i = 10; i <= 17; ++i) { + FlushRiscVReg(RiscVReg(F0 + i)); + } + for (int i = 28; i <= 31; ++i) { + FlushRiscVReg(RiscVReg(F0 + i)); + } +} + void RiscVRegCacheFPU::FlushAll() { if (!pendingFlush_) { // Nothing allocated. FPU regs are not nearly as common as GPR. diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.h b/Core/MIPS/RiscV/RiscVRegCacheFPU.h index 934e312ca7..57d93fdb83 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.h +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.h @@ -64,11 +64,12 @@ public: void MapInIn(IRRegIndex rd, IRRegIndex rs); void MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad = true); void MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad = true); - void Map4Dirty(IRRegIndex rdbase, bool avoidLoad = true); void Map4DirtyIn(IRRegIndex rdbase, IRRegIndex rsbase, bool avoidLoad = true); void Map4DirtyInIn(IRRegIndex rdbase, IRRegIndex rsbase, IRRegIndex rtbase, bool avoidLoad = true); + void FlushBeforeCall(); void FlushAll(); void FlushR(IRRegIndex r); + void FlushRiscVReg(RiscVGen::RiscVReg r); void DiscardR(IRRegIndex r); RiscVGen::RiscVReg R(int preg); // Returns a cached register @@ -78,7 +79,6 @@ private: RiscVGen::RiscVReg AllocateReg(); RiscVGen::RiscVReg FindBestToSpill(bool unusedOnly, bool *clobbered); RiscVGen::RiscVReg RiscVRegForFlush(IRRegIndex r); - void FlushRiscVReg(RiscVGen::RiscVReg r); int GetMipsRegOffset(IRRegIndex r); bool IsValidReg(IRRegIndex r) const; From 6d4fb949c2c149556c08fe7dbf8df25be48e34cf Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 10:28:00 -0700 Subject: [PATCH 134/167] riscv: Implement float compare ops. --- Core/MIPS/RiscV/RiscVCompFPU.cpp | 65 +++++++++++++++++++++++++++++ Core/MIPS/RiscV/RiscVCompSystem.cpp | 4 +- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index 9d08930848..3be4d1149d 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -220,6 +220,71 @@ void RiscVJit::CompIR_FCompare(IRInst inst) { switch (inst.op) { case IROp::FCmp: + switch (inst.dest) { + case IRFpCompareMode::False: + gpr.SetImm(IRREG_FPCOND, 0); + break; + + case IRFpCompareMode::EitherUnordered: + fpr.MapInIn(inst.src1, inst.src2); + gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); + FCLASS(32, SCRATCH1, fpr.R(inst.src1)); + FCLASS(32, SCRATCH2, fpr.R(inst.src2)); + OR(SCRATCH1, SCRATCH1, SCRATCH2); + // NAN is 0x100 or 0x200. + ANDI(SCRATCH1, SCRATCH1, 0x300); + SNEZ(gpr.R(IRREG_FPCOND), SCRATCH1); + break; + + case IRFpCompareMode::EqualOrdered: + fpr.MapInIn(inst.src1, inst.src2); + gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); + FEQ(32, gpr.R(IRREG_FPCOND), fpr.R(inst.src1), fpr.R(inst.src2)); + break; + + case IRFpCompareMode::EqualUnordered: + fpr.MapInIn(inst.src1, inst.src2); + gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); + FEQ(32, gpr.R(IRREG_FPCOND), fpr.R(inst.src1), fpr.R(inst.src2)); + + // Now let's just OR in the unordered check. + FCLASS(32, SCRATCH1, fpr.R(inst.src1)); + FCLASS(32, SCRATCH2, fpr.R(inst.src2)); + OR(SCRATCH1, SCRATCH1, SCRATCH2); + // NAN is 0x100 or 0x200. + ANDI(SCRATCH1, SCRATCH1, 0x300); + SNEZ(SCRATCH1, SCRATCH1); + OR(gpr.R(IRREG_FPCOND), gpr.R(IRREG_FPCOND), SCRATCH1); + break; + + case IRFpCompareMode::LessEqualOrdered: + fpr.MapInIn(inst.src1, inst.src2); + gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); + FLE(32, gpr.R(IRREG_FPCOND), fpr.R(inst.src1), fpr.R(inst.src2)); + break; + + case IRFpCompareMode::LessEqualUnordered: + fpr.MapInIn(inst.src1, inst.src2); + gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); + FLT(32, gpr.R(IRREG_FPCOND), fpr.R(inst.src2), fpr.R(inst.src1)); + SEQZ(gpr.R(IRREG_FPCOND), gpr.R(IRREG_FPCOND)); + break; + + case IRFpCompareMode::LessOrdered: + fpr.MapInIn(inst.src1, inst.src2); + gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); + FLT(32, gpr.R(IRREG_FPCOND), fpr.R(inst.src1), fpr.R(inst.src2)); + break; + + case IRFpCompareMode::LessUnordered: + fpr.MapInIn(inst.src1, inst.src2); + gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); + FLE(32, gpr.R(IRREG_FPCOND), fpr.R(inst.src2), fpr.R(inst.src1)); + SEQZ(gpr.R(IRREG_FPCOND), gpr.R(IRREG_FPCOND)); + break; + } + break; + case IROp::FCmovVfpuCC: case IROp::FCmpVfpuBit: case IROp::FCmpVfpuAggregate: diff --git a/Core/MIPS/RiscV/RiscVCompSystem.cpp b/Core/MIPS/RiscV/RiscVCompSystem.cpp index 2413bb55c3..863fbd103d 100644 --- a/Core/MIPS/RiscV/RiscVCompSystem.cpp +++ b/Core/MIPS/RiscV/RiscVCompSystem.cpp @@ -101,7 +101,7 @@ void RiscVJit::CompIR_Transfer(IRInst inst) { break; case IROp::SetCtrlVFPUFReg: - gpr.MapReg(IRREG_VFPU_CTRL_BASE + inst.dest, MIPSMap::NOINIT); + gpr.MapReg(IRREG_VFPU_CTRL_BASE + inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); fpr.MapReg(inst.src1); FMV(FMv::X, FMv::W, gpr.R(IRREG_VFPU_CTRL_BASE + inst.dest), fpr.R(inst.src1)); break; @@ -167,7 +167,7 @@ void RiscVJit::CompIR_Transfer(IRInst inst) { break; case IROp::FMovToGPR: - gpr.MapReg(inst.dest, MIPSMap::NOINIT); + gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); fpr.MapReg(inst.src1); FMV(FMv::X, FMv::W, gpr.R(inst.dest), fpr.R(inst.src1)); break; From 6aa4b0c5e11c4c70d7b0a7de82ea300db7bffaf5 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 19:13:12 -0700 Subject: [PATCH 135/167] irjit: Fix vmin/vmax nan handling. Should be relevant to NFS MW and possibly other game bugs. --- Core/MIPS/IR/IRInterpreter.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 561adf6f25..cb752d35b1 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -768,12 +768,28 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { mips->f[inst->dest] = mips->f[inst->src1] / mips->f[inst->src2]; break; case IROp::FMin: - // TODO: This doesn't handle VFPU ordering right. - mips->f[inst->dest] = std::min(mips->f[inst->src1], mips->f[inst->src2]); + if (my_isnan(mips->f[inst->src1]) || my_isnan(mips->f[inst->src2])) { + // See interpreter for this logic: this is for vmin, we're comparing mantissa+exp. + if (mips->fi[inst->src1] < 0 && mips->fi[inst->src2] < 0) { + mips->fi[inst->dest] = std::max(mips->fi[inst->src1], mips->fi[inst->src2]); + } else { + mips->fi[inst->dest] = std::min(mips->fi[inst->src1], mips->fi[inst->src2]); + } + } else { + mips->f[inst->dest] = std::min(mips->f[inst->src1], mips->f[inst->src2]); + } break; case IROp::FMax: - // TODO: This doesn't handle VFPU ordering right. - mips->f[inst->dest] = std::max(mips->f[inst->src1], mips->f[inst->src2]); + if (my_isnan(mips->f[inst->src1]) || my_isnan(mips->f[inst->src2])) { + // See interpreter for this logic: this is for vmax, we're comparing mantissa+exp. + if (mips->fi[inst->src1] < 0 && mips->fi[inst->src2] < 0) { + mips->fi[inst->dest] = std::min(mips->fi[inst->src1], mips->fi[inst->src2]); + } else { + mips->fi[inst->dest] = std::max(mips->fi[inst->src1], mips->fi[inst->src2]); + } + } else { + mips->f[inst->dest] = std::max(mips->f[inst->src1], mips->f[inst->src2]); + } break; case IROp::FMov: From ec4927069e74883c06c86684621d6dce750e62bf Mon Sep 17 00:00:00 2001 From: haorui wang <1905811497@qq.com> Date: Sun, 30 Jul 2023 12:31:31 +0800 Subject: [PATCH 136/167] 1. remove some unused code 2. add some missing header 3. fix error address offset operation --- Common/GPU/D3D11/D3D11Loader.cpp | 2 -- Common/GPU/D3D11/D3D11Loader.h | 2 -- Common/GPU/D3D11/thin3d_d3d11.cpp | 2 +- Windows/GPU/D3D11Context.cpp | 7 ------- Windows/InputBox.cpp | 1 + 5 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Common/GPU/D3D11/D3D11Loader.cpp b/Common/GPU/D3D11/D3D11Loader.cpp index 0dbea80994..6268f99ed5 100644 --- a/Common/GPU/D3D11/D3D11Loader.cpp +++ b/Common/GPU/D3D11/D3D11Loader.cpp @@ -11,7 +11,6 @@ static HMODULE g_D3DCompileModule; LPCREATEDXGIFACTORY ptr_CreateDXGIFactory; LPD3D11CREATEDEVICE ptr_D3D11CreateDevice; -LPD3D11CREATEDEVICEANDSWAPCHAIN ptr_D3D11CreateDeviceAndSwapChain; pD3DCompile ptr_D3DCompile; LoadD3D11Error LoadD3D11() { @@ -22,7 +21,6 @@ LoadD3D11Error LoadD3D11() { g_D3D11Module = LoadLibrary(L"d3d11.dll"); if (g_D3D11Module) { ptr_D3D11CreateDevice = (LPD3D11CREATEDEVICE)GetProcAddress(g_D3D11Module, "D3D11CreateDevice"); - ptr_D3D11CreateDeviceAndSwapChain = (LPD3D11CREATEDEVICEANDSWAPCHAIN)GetProcAddress(g_D3D11Module, "D3D11CreateDeviceAndSwapChain"); } else { return LoadD3D11Error::FAIL_NO_D3D11; } diff --git a/Common/GPU/D3D11/D3D11Loader.h b/Common/GPU/D3D11/D3D11Loader.h index 750d1da980..080f761c57 100644 --- a/Common/GPU/D3D11/D3D11Loader.h +++ b/Common/GPU/D3D11/D3D11Loader.h @@ -14,12 +14,10 @@ #endif typedef HRESULT (WINAPI *LPCREATEDXGIFACTORY)(REFIID, void **); -typedef HRESULT (WINAPI *LPD3D11CREATEDEVICEANDSWAPCHAIN)(__in_opt IDXGIAdapter *pAdapter, D3D_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, __in_ecount_opt(FeatureLevels) CONST D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, __in_opt CONST DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, __out_opt IDXGISwapChain **ppSwapChain, __out_opt ID3D11Device **ppDevice, __out_opt D3D_FEATURE_LEVEL *pFeatureLevel, __out_opt ID3D11DeviceContext **ppImmediateContext); typedef HRESULT (WINAPI *LPD3D11CREATEDEVICE)(IDXGIAdapter *, D3D_DRIVER_TYPE, HMODULE, UINT32, D3D_FEATURE_LEVEL *, UINT, UINT32, ID3D11Device **, D3D_FEATURE_LEVEL *, ID3D11DeviceContext **); extern LPCREATEDXGIFACTORY ptr_CreateDXGIFactory; extern LPD3D11CREATEDEVICE ptr_D3D11CreateDevice; -extern LPD3D11CREATEDEVICEANDSWAPCHAIN ptr_D3D11CreateDeviceAndSwapChain; extern pD3DCompile ptr_D3DCompile; enum class LoadD3D11Error { diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp index 378829f578..3cad78701b 100644 --- a/Common/GPU/D3D11/thin3d_d3d11.cpp +++ b/Common/GPU/D3D11/thin3d_d3d11.cpp @@ -842,7 +842,7 @@ bool D3D11Texture::FillLevel(ID3D11DeviceContext *context, int level, int w, int for (int y = 0; y < h; ++y) { void *dest = (uint8_t *)mapped.pData + mapped.DepthPitch * s + mapped.RowPitch * y; uint32_t byteStride = w * (uint32_t)DataFormatSizeInBytes(format_); - const void *src = data[level] + byteStride * (y + h * d); + const void *src = data[level] + byteStride * (y + h * s); memcpy(dest, src, byteStride); } } diff --git a/Windows/GPU/D3D11Context.cpp b/Windows/GPU/D3D11Context.cpp index ed503ab2fc..7319f989f9 100644 --- a/Windows/GPU/D3D11Context.cpp +++ b/Windows/GPU/D3D11Context.cpp @@ -59,13 +59,6 @@ HRESULT D3D11Context::CreateTheDevice(IDXGIAdapter *adapter) { UINT createDeviceFlags = 0; #endif - static const D3D_DRIVER_TYPE driverTypes[] = { - D3D_DRIVER_TYPE_HARDWARE, - D3D_DRIVER_TYPE_WARP, - D3D_DRIVER_TYPE_REFERENCE, - }; - const UINT numDriverTypes = ARRAYSIZE(driverTypes); - static const D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_12_1, D3D_FEATURE_LEVEL_12_0, diff --git a/Windows/InputBox.cpp b/Windows/InputBox.cpp index 9db0b10dbd..49cb2eb4e2 100644 --- a/Windows/InputBox.cpp +++ b/Windows/InputBox.cpp @@ -1,3 +1,4 @@ +#include "Common/Common.h" #include "Common/CommonTypes.h" #include "Common/CommonWindows.h" #include "Windows/InputBox.h" From 99d5a90898ef11ba5d02f7f3986b577e85ff703b Mon Sep 17 00:00:00 2001 From: Narugakuruga <31060534+Narugakuruga@users.noreply.github.com> Date: Sun, 30 Jul 2023 14:23:21 +0800 Subject: [PATCH 137/167] Update zh_CN.ini --- assets/lang/zh_CN.ini | 142 +++++++++++++++++++++--------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index da6fa6a85a..2d616bc62a 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -1,42 +1,42 @@ [Achievements] -%1: Leaderboard attempt failed = %1: Leaderboard attempt failed -%1: Leaderboard attempt started = %1: Leaderboard attempt started -%d achievements, %d points = %d achievements, %d points -Account = Account -Achievement Unlocked = Achievement Unlocked -Achievements = Achievements -Achievements are disabled = Achievements are disabled -Can't log in to RetroAchievements right now = Can't log in to RetroAchievements right now -Challenge Mode = Challenge Mode -Challenge Mode (no savestates) = Challenge Mode (no savestates) -Contacting RetroAchievements server... = Contacting RetroAchievements server... -Customize = Customize -Earned = You have earned %d of %d achievements, and %d of %d points -Encore Mode = Encore Mode -How to use RetroAchievements = How to use RetroAchievements -In Encore mode - listings may be wrong below = In Encore mode - listings may be wrong below -Leaderboard score submission = Leaderboard score submission -Leaderboard submission is enabled = Leaderboard submission is enabled -Leaderboards = Leaderboards -Links = Links -Locked achievements = Locked achievements -Log bad memory accesses = Log bad memory accesses +%1: Leaderboard attempt failed = %1: 排名失败 +%1: Leaderboard attempt started = %1: 开始尝试排名 +%d achievements, %d points = %d 成就, %d 点数 +Account = 账号 +Achievement Unlocked = 成就达成! +Achievements = 成就 +Achievements are disabled = 未启用成就功能 +Can't log in to RetroAchievements right now = 暂时无法登录到RetroAchievements +Challenge Mode = 挑战模式 +Challenge Mode (no savestates) = 挑战模式 (无即时存档) +Contacting RetroAchievements server... = 连接RetroAchievements服务器... +Customize = 自定义 +Earned = 你已经达成%d个成就,总共有%d个可解锁, %d点数,总共%d点 +Encore Mode = 回味模式 +How to use RetroAchievements = 如何使用RetroAchievements +In Encore mode - listings may be wrong below = 回味模式 - 下面列表中可能错误 +Leaderboard score submission = 排名得分提交 +Leaderboard submission is enabled = 排名上传已启用 +Leaderboards = 排行榜 +Links = 链接 +Locked achievements = 可解锁的成就 +Log bad memory accesses = 记录损坏的内存访问 Mastered %1 = Mastered %1 -Register on www.retroachievements.org = Register on www.retroachievements.org -RetroAchievements are not available for this game = RetroAchievements are not available for this game -RetroAchievements website = RetroAchievements website -Rich Presence = Rich Presence -Save state loaded without achievement data = Save state loaded without achievement data -Save states not available in Challenge Mode = Save states not available in Challenge Mode -Sound Effects = Sound Effects -Statistics = Statistics -Submitted %1 for %2 = Submitted %1 for %2 -Syncing achievements data... = Syncing achievements data... -Test Mode = Test Mode -This feature is not available in Challenge Mode = This feature is not available in Challenge Mode -This game has no achievements = This game has no achievements -Unlocked achievements = Unlocked achievements -Unofficial achievements = Unofficial achievements +Register on www.retroachievements.org = 在www.retroachievements.org注册 +RetroAchievements are not available for this game = RetroAchievements暂不适用于当前游戏 +RetroAchievements website = RetroAchievements网站 +Rich Presence = 展示详细信息 +Save state loaded without achievement data = 读取即时存档时不带成就数据 +Save states not available in Challenge Mode = 挑战模式下禁用即时存档 +Sound Effects = 音效 +Statistics = 统计 +Submitted %1 for %2 = 对%2提交%1中 +Syncing achievements data... = 同步成就数据中... +Test Mode = 测试模式 +This feature is not available in Challenge Mode = 挑战模式下此功能不可用 +This game has no achievements = 此游戏没有成就列表 +Unlocked achievements = 解锁的成就 +Unofficial achievements = 自定义成就 [Audio] Alternate speed volume = 备选速度时的音量 @@ -91,7 +91,7 @@ Disable diagonal input = 禁用对角线方向输入 Double tap = 双击 Enable gesture control = 启用体感操作 Enable standard shortcut keys = 启用标准快捷键 -frames = frames +frames = 帧 Gesture = 体感 Gesture mapping = 体感映射 Glowing borders = 细边框(发光) @@ -120,7 +120,7 @@ OnScreen = 屏幕虚拟键 Portrait = 纵向 Portrait Reversed = 纵向反转 PSP Action Buttons = PSP 动作键 -Rapid fire interval = Rapid fire interval +Rapid fire interval = 连发间隔 Raw input = 原始输入 Repeat mode = 连发模式 Reset to defaults = 重置为默认 @@ -209,7 +209,7 @@ Log Console = 控制台日志 (&L) Memory View... = 查看内存 (&V)... More Settings... = 更多设置 (&M)... Nearest = 邻近取样 (&N) -Recent = &Recent +Recent = 最近游戏(&R) Skip Buffer Effects = 跳过缓冲效果(更快) (&S) Off = 关闭 (&O) Open Chat = 聊天窗口 @@ -317,7 +317,7 @@ Active = 激活 Back = 返回 Cancel = 取消 Center = 中心 -Changing this setting requires PPSSPP to restart. = 更改此设置需要重新启动 PPSSPP。 +Changing this setting requires PPSSPP to restart. = 设置生效需要重新启动 PPSSPP。 Channel: = 信道: Choose PPSSPP save folder = 选择PPSSPP存档的文件夹 Confirm Overwrite = 您要覆盖这个存档吗? @@ -337,14 +337,14 @@ DeleteConfirmGameConfig = 您确定要删除此游戏的设置吗? DeleteFailed = 无法删除存档。 Deleting = 删除中\n请稍候... Disable All = 全部禁用 -Disabled = Disabled +Disabled = 禁用 Dumps = 转储 Edit = 编辑 Enable All = 全部启用 -Enabled = Enabled +Enabled = 启用 Enter = 确定 -Failed to connect to server, check your internet connection. = Failed to connect to server, check your internet connection. -Failed to log in, check your username and password. = Failed to log in, check your username and password. +Failed to connect to server, check your internet connection. = 未连接到服务器,请检查网络情况 +Failed to log in, check your username and password. = 无法登录,请检查账号密码 Filter = 筛选 Finish = 完成 GE Frame Dumps = GE帧转储 @@ -355,10 +355,10 @@ Load = 载入 Load completed = 已载入。 Loading = 载入中\n请稍候... LoadingFailed = 无法载入存档。 -Log in = Log in -Log out = Log out -Logged in! = Logged in! -Logging in... = Logging in... +Log in = 登录 +Log out = 退出 +Logged in! = 登录成功! +Logging in... = 登录中... Move = 移动位置 Move Down = 移至下层 Move Up = 移至上层 @@ -369,11 +369,11 @@ ObtainingIP = 获取IP地址.\n请稍候... OK = 确定 Old savedata detected = 检测到旧版本的存档数据 Options = 选项 -Password = Password +Password = 密码 Remove = 删除 Reset = 重置 Resize = 调整大小 -Restart = 重新开始 +Restart = 重新启动 Retry = 重试 Save = 保存 Save completed = 保存完成。 @@ -382,7 +382,7 @@ SavingFailed = 无法保存存档。 Search = 搜索 seconds, 0:off = 经过几秒,0为"从不" Select = 选择 -Settings = Settings +Settings = 设置 Shift = 翻页 Skip = 跳过 Snap = 网格对齐 @@ -394,7 +394,7 @@ There is no data = 存档不存在。 Toggle All = 全选/不选 Toggle List = 显示列表 Unsupported = 不支持 -Username = Username +Username = 用户名 When you save, it will load on a PSP, but not an older PPSSPP = 当您保存后, 它只会在PSP实机上载入, 而不是旧版本的PPSSPP When you save, it will not work on outdated PSP Firmware anymore = 当您保存后, 它无法在过时的PSP固件上运行 Yes = 是 @@ -609,7 +609,7 @@ Stereo rendering = 立体渲染 Stretch = 拉伸 Texture Filter = 纹理过滤方法 Texture Filtering = 纹理过滤 -Texture replacement pack activated = Texture replacement pack activated +Texture replacement pack activated = 已使用纹理替换包 Texture Scaling = 纹理缩放 Texture Shader = 纹理着色器 Turn off Hardware Tessellation - unsupported = 关闭"硬件曲面细分":不支持 @@ -638,12 +638,12 @@ Zip archive corrupt = ZIP 文档损坏 Zip file does not contain PSP software = ZIP文件不包含PSP软件 [KeyMapping] -Allow combo mappings = Allow combo mappings +Allow combo mappings = 多键合一 Autoconfigure = 自动配置 Autoconfigure for device = 根据设备配置 Bind All = 全部绑定 Clear All = 全部清除 -Combo mappings are not enabled = Combo mappings are not enabled +Combo mappings are not enabled = 请打开多键合一 Default All = 恢复默认 Map a new key for = 输入一个新按键: Map Key = 按键映射 @@ -862,20 +862,20 @@ You're in Offline Mode, go to lobby or online hall = 您现在处于离线模式 [PSPSettings] Auto = Auto -Chinese (simplified) = Chinese (simplified) -Chinese (traditional) = Chinese (traditional) -Dutch = Dutch -English = English -French = French -Game language = Game language -German = German -Italian = Italian -Japanese = Japanese -Korean = Korean -Games often don't support all languages = Games often don't support all languages -Portuguese = Portuguese -Russian = Russian -Spanish = Spanish +Chinese (simplified) = 简体中文 +Chinese (traditional) = 繁体中文 +Dutch = 荷兰语 +English = 英语 +French = 法语 +Game language = 游戏语言 +German = 德语 +Italian = 意大利语 +Japanese = 日语 +Korean = 韩语 +Games often don't support all languages = 仅较少游戏支持切换语言 +Portuguese = 葡萄牙语 +Russian = 俄语 +Spanish = 西班牙语 [Pause] Cheats = 金手指 @@ -924,7 +924,7 @@ Saturation = 色彩 Scanlines = 扫描线(CRT) Sharpen = 锐化 SideBySide = 双目渲染 -SSAA(Gauss) = SSAA(超级采样抗锯齿-高斯) +SSAA(Gauss) = SSAA(超级采样抗锯齿-高斯采样) Strength = 强度 Tex4xBRZ = 4xBRZ TexMMPX = MMPX From a5671bc716d84b80ad8e2c442afa573b0c290b18 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 10:44:57 -0700 Subject: [PATCH 138/167] riscv: Add simple debug log of missed ops. --- Core/MIPS/RiscV/RiscVCompSystem.cpp | 10 ---- Core/MIPS/RiscV/RiscVJit.cpp | 71 +++++++++++++++++++++++++++++ Core/MIPS/RiscV/RiscVJit.h | 1 + 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompSystem.cpp b/Core/MIPS/RiscV/RiscVCompSystem.cpp index 863fbd103d..727465fad0 100644 --- a/Core/MIPS/RiscV/RiscVCompSystem.cpp +++ b/Core/MIPS/RiscV/RiscVCompSystem.cpp @@ -20,7 +20,6 @@ #include "Core/HLE/HLE.h" #include "Core/HLE/ReplaceTables.h" #include "Core/MemMap.h" -#include "Core/MIPS/MIPSTables.h" #include "Core/MIPS/RiscV/RiscVJit.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" @@ -182,15 +181,6 @@ void RiscVJit::CompIR_System(IRInst inst) { CONDITIONAL_DISABLE; switch (inst.op) { - case IROp::Interpret: - // IR protects us against this being a branching instruction (well, hopefully.) - FlushAll(); - SaveStaticRegisters(); - LI(X10, (int32_t)inst.constant); - QuickCallFunction((const u8 *)MIPSGetInterpretFunc(MIPSOpcode(inst.constant))); - LoadStaticRegisters(); - break; - case IROp::Syscall: FlushAll(); SaveStaticRegisters(); diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 4ef111f7ef..8b69515a54 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -16,7 +16,9 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "Common/StringUtils.h" +#include "Common/TimeUtil.h" #include "Core/MemMap.h" +#include "Core/MIPS/MIPSTables.h" #include "Core/MIPS/RiscV/RiscVJit.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" #include "Common/Profiler/Profiler.h" @@ -28,6 +30,45 @@ using namespace RiscVJitConstants; static constexpr bool enableDebug = false; +static std::map debugSeenNotCompiledIR; +static std::map debugSeenNotCompiled; +double lastDebugLog = 0.0; + +static void LogDebugNotCompiled() { + if (!enableDebug) + return; + + double now = time_now_d(); + if (now < lastDebugLog + 1.0) + return; + lastDebugLog = now; + + int worstIROp = -1; + int worstIRVal = 0; + for (auto it : debugSeenNotCompiledIR) { + if (it.second > worstIRVal) { + worstIRVal = it.second; + worstIROp = it.first; + } + } + debugSeenNotCompiledIR.clear(); + + const char *worstName = nullptr; + int worstVal = 0; + for (auto it : debugSeenNotCompiled) { + if (it.second > worstVal) { + worstVal = it.second; + worstName = it.first; + } + } + debugSeenNotCompiled.clear(); + + if (worstIROp != -1) + WARN_LOG(JIT, "Most not compiled IR op: %s (%d)", GetIRMeta((IROp)worstIROp)->name, worstIRVal); + if (worstName != nullptr) + WARN_LOG(JIT, "Most not compiled op: %s (%d)", worstName, worstVal); +} + RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo), fpr(mipsState, &jo) { // Automatically disable incompatible options. if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) { @@ -49,6 +90,10 @@ RiscVJit::~RiscVJit() { } void RiscVJit::RunLoopUntil(u64 globalticks) { + if constexpr (enableDebug) { + LogDebugNotCompiled(); + } + PROFILE_THIS_SCOPE("jit"); ((void (*)())enterDispatcher_)(); } @@ -335,6 +380,9 @@ void RiscVJit::CompileIRInst(IRInst inst) { break; case IROp::Interpret: + CompIR_Interpret(inst); + break; + case IROp::Syscall: case IROp::CallReplacement: case IROp::Break: @@ -381,6 +429,9 @@ static u32 DoIRInst(uint64_t value) { IRInst inst; memcpy(&inst, &value, sizeof(inst)); + if constexpr (enableDebug) + debugSeenNotCompiledIR[(uint8_t)inst.op]++; + return IRInterpret(currentMIPS, &inst, 1); } @@ -409,6 +460,26 @@ void RiscVJit::CompIR_Generic(IRInst inst) { } } +static void DebugInterpretHit(const char *name) { + if (enableDebug) + debugSeenNotCompiled[name]++; +} + +void RiscVJit::CompIR_Interpret(IRInst inst) { + MIPSOpcode op(inst.constant); + + // IR protects us against this being a branching instruction (well, hopefully.) + FlushAll(); + SaveStaticRegisters(); + if (enableDebug) { + LI(X10, MIPSGetName(op)); + QuickCallFunction(&DebugInterpretHit); + } + LI(X10, (int32_t)inst.constant); + QuickCallFunction((const u8 *)MIPSGetInterpretFunc(op)); + LoadStaticRegisters(); +} + void RiscVJit::FlushAll() { gpr.FlushAll(); fpr.FlushAll(); diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index 6c3958dcf7..f7f9371e62 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -86,6 +86,7 @@ private: void CompIR_FStore(IRInst inst); void CompIR_Generic(IRInst inst); void CompIR_HiLo(IRInst inst); + void CompIR_Interpret(IRInst inst); void CompIR_Load(IRInst inst); void CompIR_LoadShift(IRInst inst); void CompIR_Logic(IRInst inst); From 70ff18a4631d5e59ff50de625152bcce787c8fb7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 10:53:16 -0700 Subject: [PATCH 139/167] riscv: Implement count leading zeros. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index aa3197dbf1..1b29413797 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -254,7 +254,13 @@ void RiscVJit::CompIR_Bits(IRInst inst) { break; case IROp::Clz: - CompIR_Generic(inst); + if (cpu_info.RiscV_Zbb) { + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); + // This even sets to 32 when zero, perfect. + CLZW(gpr.R(inst.dest), gpr.R(inst.src1)); + } else { + CompIR_Generic(inst); + } break; default: From 9c9330a207a9423c1888eb01c1c9b5d12e8ccff5 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 11:05:21 -0700 Subject: [PATCH 140/167] riscv: Implement float conditional move. --- Core/MIPS/RiscV/RiscVCompFPU.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index 3be4d1149d..811fec933b 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -286,6 +286,27 @@ void RiscVJit::CompIR_FCompare(IRInst inst) { break; case IROp::FCmovVfpuCC: + gpr.MapReg(IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC); + fpr.MapDirtyIn(inst.dest, inst.src1, false); + if ((inst.src2 & 0xF) == 0) { + ANDI(SCRATCH1, gpr.R(IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC), 1); + } else if (cpu_info.RiscV_Zbs) { + BEXTI(SCRATCH1, gpr.R(IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC), inst.src2 & 0xF); + } else { + SRLI(SCRATCH1, gpr.R(IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC), inst.src2 & 0xF); + ANDI(SCRATCH1, SCRATCH1, 1); + } + if ((inst.src2 >> 7) & 1) { + FixupBranch skip = BEQ(SCRATCH1, R_ZERO); + FMV(32, fpr.R(inst.dest), fpr.R(inst.src1)); + SetJumpTarget(skip); + } else { + FixupBranch skip = BNE(SCRATCH1, R_ZERO); + FMV(32, fpr.R(inst.dest), fpr.R(inst.src1)); + SetJumpTarget(skip); + } + break; + case IROp::FCmpVfpuBit: case IROp::FCmpVfpuAggregate: CompIR_Generic(inst); From 8e8081c68628f7eda83bab6b4431f2c81ace7a8e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 12:48:20 -0700 Subject: [PATCH 141/167] riscv: Implement VFPU compares. --- Core/MIPS/RiscV/RiscVCompFPU.cpp | 121 +++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 5 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index 811fec933b..11538f4e3a 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -218,6 +218,8 @@ void RiscVJit::CompIR_FSat(IRInst inst) { void RiscVJit::CompIR_FCompare(IRInst inst) { CONDITIONAL_DISABLE; + constexpr IRRegIndex IRREG_VFPUL_CC = IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC; + switch (inst.op) { case IROp::FCmp: switch (inst.dest) { @@ -286,14 +288,14 @@ void RiscVJit::CompIR_FCompare(IRInst inst) { break; case IROp::FCmovVfpuCC: - gpr.MapReg(IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC); + gpr.MapReg(IRREG_VFPUL_CC); fpr.MapDirtyIn(inst.dest, inst.src1, false); if ((inst.src2 & 0xF) == 0) { - ANDI(SCRATCH1, gpr.R(IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC), 1); + ANDI(SCRATCH1, gpr.R(IRREG_VFPUL_CC), 1); } else if (cpu_info.RiscV_Zbs) { - BEXTI(SCRATCH1, gpr.R(IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC), inst.src2 & 0xF); + BEXTI(SCRATCH1, gpr.R(IRREG_VFPUL_CC), inst.src2 & 0xF); } else { - SRLI(SCRATCH1, gpr.R(IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC), inst.src2 & 0xF); + SRLI(SCRATCH1, gpr.R(IRREG_VFPUL_CC), inst.src2 & 0xF); ANDI(SCRATCH1, SCRATCH1, 1); } if ((inst.src2 >> 7) & 1) { @@ -308,8 +310,117 @@ void RiscVJit::CompIR_FCompare(IRInst inst) { break; case IROp::FCmpVfpuBit: + gpr.MapReg(IRREG_VFPUL_CC, MIPSMap::DIRTY); + + switch (VCondition(inst.dest & 0xF)) { + case VC_EQ: + fpr.MapInIn(inst.src1, inst.src2); + FEQ(32, SCRATCH1, fpr.R(inst.src1), fpr.R(inst.src2)); + break; + case VC_NE: + fpr.MapInIn(inst.src1, inst.src2); + // We could almost negate FEQ, except NAN != NAN. + // Anything != NAN is false and NAN != NAN is within that, so we only check one side. + FCLASS(32, SCRATCH2, fpr.R(inst.src2)); + // NAN is 0x100 or 0x200. + ANDI(SCRATCH2, SCRATCH2, 0x300); + SNEZ(SCRATCH2, SCRATCH2); + + FEQ(32, SCRATCH1, fpr.R(inst.src1), fpr.R(inst.src2)); + SEQZ(SCRATCH1, SCRATCH1); + // Just OR in whether that side was a NAN so it's always not equal. + OR(SCRATCH1, SCRATCH1, SCRATCH2); + break; + case VC_LT: + fpr.MapInIn(inst.src1, inst.src2); + FLT(32, SCRATCH1, fpr.R(inst.src1), fpr.R(inst.src2)); + break; + case VC_LE: + fpr.MapInIn(inst.src1, inst.src2); + FLE(32, SCRATCH1, fpr.R(inst.src1), fpr.R(inst.src2)); + break; + case VC_GT: + fpr.MapInIn(inst.src1, inst.src2); + FLT(32, SCRATCH1, fpr.R(inst.src2), fpr.R(inst.src1)); + break; + case VC_GE: + fpr.MapInIn(inst.src1, inst.src2); + FLE(32, SCRATCH1, fpr.R(inst.src2), fpr.R(inst.src1)); + break; + case VC_EZ: + case VC_NZ: + fpr.MapReg(inst.src1); + // Zero is either 0x10 or 0x08. + FCLASS(32, SCRATCH1, gpr.R(inst.src1)); + ANDI(SCRATCH1, SCRATCH1, 0x18); + if ((inst.dest & 4) == 0) + SNEZ(SCRATCH1, SCRATCH1); + else + SEQZ(SCRATCH1, SCRATCH1); + break; + case VC_EN: + case VC_NN: + fpr.MapReg(inst.src1); + // NAN is either 0x100 or 0x200. + FCLASS(32, SCRATCH1, gpr.R(inst.src1)); + ANDI(SCRATCH1, SCRATCH1, 0x300); + if ((inst.dest & 4) == 0) + SNEZ(SCRATCH1, SCRATCH1); + else + SEQZ(SCRATCH1, SCRATCH1); + break; + case VC_EI: + case VC_NI: + fpr.MapReg(inst.src1); + // Infinity is either 0x80 or 0x01. + FCLASS(32, SCRATCH1, gpr.R(inst.src1)); + ANDI(SCRATCH1, SCRATCH1, 0x81); + if ((inst.dest & 4) == 0) + SNEZ(SCRATCH1, SCRATCH1); + else + SEQZ(SCRATCH1, SCRATCH1); + break; + case VC_ES: + case VC_NS: + fpr.MapReg(inst.src1); + // Infinity is either 0x80 or 0x01, NAN is either 0x100 or 0x200. + FCLASS(32, SCRATCH1, gpr.R(inst.src1)); + ANDI(SCRATCH1, SCRATCH1, 0x381); + if ((inst.dest & 4) == 0) + SNEZ(SCRATCH1, SCRATCH1); + else + SEQZ(SCRATCH1, SCRATCH1); + break; + case VC_TR: + LI(SCRATCH1, 1); + break; + case VC_FL: + LI(SCRATCH1, 0); + break; + } + + ANDI(gpr.R(IRREG_VFPUL_CC), gpr.R(IRREG_VFPUL_CC), ~(1 << (inst.dest >> 4))); + if ((inst.dest >> 4) != 0) + SLLI(SCRATCH1, SCRATCH1, inst.dest >> 4); + OR(gpr.R(IRREG_VFPUL_CC), gpr.R(IRREG_VFPUL_CC), SCRATCH1); + break; + case IROp::FCmpVfpuAggregate: - CompIR_Generic(inst); + gpr.MapReg(IRREG_VFPUL_CC, MIPSMap::DIRTY); + ANDI(SCRATCH1, gpr.R(IRREG_VFPUL_CC), inst.dest); + // This is the "any bit", easy. + SNEZ(SCRATCH2, SCRATCH1); + // To compare to inst.dest for "all", let's simply subtract it and compare to zero. + ADDI(SCRATCH1, SCRATCH1, -inst.dest); + SEQZ(SCRATCH1, SCRATCH1); + // Now we combine those together. + SLLI(SCRATCH1, SCRATCH1, 5); + SLLI(SCRATCH2, SCRATCH2, 4); + OR(SCRATCH1, SCRATCH1, SCRATCH2); + + // Reject those any/all bits and replace them with our own. + ANDI(gpr.R(IRREG_VFPUL_CC), gpr.R(IRREG_VFPUL_CC), ~0x30); + OR(gpr.R(IRREG_VFPUL_CC), gpr.R(IRREG_VFPUL_CC), SCRATCH1); break; default: From 0036f3c494aed5fc6a2e0e56946136940aa03dc0 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 13:48:27 -0700 Subject: [PATCH 142/167] riscv: Implement FMin/FMax. --- Core/MIPS/RiscV/RiscVCompFPU.cpp | 70 +++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index 11538f4e3a..d9a2120ae6 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -110,18 +110,68 @@ void RiscVJit::CompIR_FArith(IRInst inst) { void RiscVJit::CompIR_FCondAssign(IRInst inst) { CONDITIONAL_DISABLE; - - switch (inst.op) { - case IROp::FMin: - case IROp::FMax: - // TODO: These are tricky, have to handle order correctly. - CompIR_Generic(inst); - break; - - default: + if (inst.op != IROp::FMin && inst.op != IROp::FMax) INVALIDOP; - break; + bool maxCondition = inst.op == IROp::FMax; + + // FMin and FMax are used by VFPU and handle NAN/INF as just a larger exponent. + fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + FCLASS(32, SCRATCH1, fpr.R(inst.src1)); + FCLASS(32, SCRATCH2, fpr.R(inst.src2)); + + // If either side is a NAN, it needs to participate in the comparison. + OR(SCRATCH1, SCRATCH1, SCRATCH2); + // NAN is either 0x100 or 0x200. + ANDI(SCRATCH1, SCRATCH1, 0x300); + FixupBranch useNormalCond = BEQ(SCRATCH1, R_ZERO); + + // Time to use bits... classify won't help because it ignores -NAN. + FMV(FMv::X, FMv::W, SCRATCH1, fpr.R(inst.src1)); + FMV(FMv::X, FMv::W, SCRATCH2, fpr.R(inst.src2)); + + // If both are negative, we flip the comparison (not two's compliment.) + // We cheat and use RA... + AND(R_RA, SCRATCH1, SCRATCH2); + SRLIW(R_RA, R_RA, 31); + + if (cpu_info.RiscV_Zbb) { + FixupBranch swapCompare = BNE(R_RA, R_ZERO); + if (maxCondition) + MAX(SCRATCH1, SCRATCH1, SCRATCH2); + else + MIN(SCRATCH1, SCRATCH1, SCRATCH2); + FixupBranch skipSwapCompare = J(); + SetJumpTarget(swapCompare); + if (maxCondition) + MIN(SCRATCH1, SCRATCH1, SCRATCH2); + else + MAX(SCRATCH1, SCRATCH1, SCRATCH2); + SetJumpTarget(skipSwapCompare); + } else { + RiscVReg isSrc1LowerReg = gpr.GetAndLockTempR(); + gpr.ReleaseSpillLocksAndDiscardTemps(); + + SLT(isSrc1LowerReg, SCRATCH1, SCRATCH2); + // Flip the flag (to reverse the min/max) based on if both were negative. + XOR(isSrc1LowerReg, isSrc1LowerReg, R_RA); + FixupBranch useSrc1; + if (maxCondition) + useSrc1 = BEQ(isSrc1LowerReg, R_ZERO); + else + useSrc1 = BNE(isSrc1LowerReg, R_ZERO); + MV(SCRATCH1, SCRATCH2); + SetJumpTarget(useSrc1); } + + FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1); + FixupBranch finish = J(); + + SetJumpTarget(useNormalCond); + if (maxCondition) + FMAX(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2)); + else + FMIN(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2)); + SetJumpTarget(finish); } void RiscVJit::CompIR_FAssign(IRInst inst) { From 26a527bdf826b28de932a0f3b9e810687b2fdae8 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 08:47:26 -0700 Subject: [PATCH 143/167] riscv: Implement float/int conversion. --- Common/RiscVEmitter.cpp | 1 + Core/MIPS/RiscV/RiscVCompFPU.cpp | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Common/RiscVEmitter.cpp b/Common/RiscVEmitter.cpp index 0029da5287..5c986bdf81 100644 --- a/Common/RiscVEmitter.cpp +++ b/Common/RiscVEmitter.cpp @@ -2148,6 +2148,7 @@ void RiscVEmitter::FCVT(FConv to, FConv from, RiscVReg rd, RiscVReg rs1, Round r _assert_msg_(rm == Round::DYNAMIC || rm == Round::NEAREST_EVEN, "Invalid rounding mode for widening FCVT"); rm = Round::NEAREST_EVEN; } + _assert_msg_(fromFmt != toFmt, "FCVT cannot convert to same float type"); Write32(EncodeR(Opcode32::OP_FP, rd, (Funct3)rm, rs1, (RiscVReg)fromFmt, toFmt, Funct5::FCVT_SZ)); } else { Funct5 funct5 = FConvToIntegerBits(to) == 0 ? Funct5::FCVT_FROMX : Funct5::FCVT_TOX; diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index c534e35533..ead8244712 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -221,18 +221,32 @@ void RiscVJit::CompIR_FAssign(IRInst inst) { void RiscVJit::CompIR_FRound(IRInst inst) { CONDITIONAL_DISABLE; + // TODO: If this is followed by a GPR transfer, might want to combine. + fpr.MapDirtyIn(inst.dest, inst.src1); + switch (inst.op) { case IROp::FRound: + FCVT(FConv::W, FConv::S, SCRATCH1, fpr.R(inst.src1), Round::NEAREST_EVEN); + break; + case IROp::FTrunc: + FCVT(FConv::W, FConv::S, SCRATCH1, fpr.R(inst.src1), Round::TOZERO); + break; + case IROp::FCeil: + FCVT(FConv::W, FConv::S, SCRATCH1, fpr.R(inst.src1), Round::UP); + break; + case IROp::FFloor: - CompIR_Generic(inst); + FCVT(FConv::W, FConv::S, SCRATCH1, fpr.R(inst.src1), Round::DOWN); break; default: INVALIDOP; break; } + + FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1); } void RiscVJit::CompIR_FCvt(IRInst inst) { @@ -241,11 +255,17 @@ void RiscVJit::CompIR_FCvt(IRInst inst) { switch (inst.op) { case IROp::FCvtWS: case IROp::FCvtScaledWS: - case IROp::FCvtSW: case IROp::FCvtScaledSW: CompIR_Generic(inst); break; + case IROp::FCvtSW: + // TODO: This is probably proceeded by a GPR transfer, might be ideal to combine. + fpr.MapDirtyIn(inst.dest, inst.src1); + FMV(FMv::X, FMv::W, SCRATCH1, fpr.R(inst.src1)); + FCVT(FConv::S, FConv::W, fpr.R(inst.dest), SCRATCH1); + break; + default: INVALIDOP; break; From c24e3ef83106bbb3b00a5334ccf7a97743981445 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 29 Jul 2023 17:53:15 -0700 Subject: [PATCH 144/167] riscv: Implement ll/sc. --- Core/MIPS/IR/IRInst.h | 1 + Core/MIPS/RiscV/RiscVCompLoadStore.cpp | 40 ++++++++++++++++++++++++-- Core/MIPS/RiscV/RiscVRegCache.cpp | 3 +- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 5a2a2e25a1..795dc75233 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -315,6 +315,7 @@ enum : IRReg { IRREG_HI = 243, IRREG_FCR31 = 244, IRREG_FPCOND = 245, + IRREG_LLBIT = 250, }; enum IRFlags { diff --git a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp index 8cc8af74de..d6a49e6f13 100644 --- a/Core/MIPS/RiscV/RiscVCompLoadStore.cpp +++ b/Core/MIPS/RiscV/RiscVCompLoadStore.cpp @@ -113,7 +113,9 @@ void RiscVJit::CompIR_Load(IRInst inst) { break; case IROp::Load32Linked: - CompIR_Generic(inst); + if (inst.dest != MIPS_REG_ZERO) + LW(gpr.R(inst.dest), addrReg, imm); + gpr.SetImm(IRREG_LLBIT, 1); break; default: @@ -258,7 +260,41 @@ void RiscVJit::CompIR_CondStore(IRInst inst) { if (inst.op != IROp::Store32Conditional) INVALIDOP; - CompIR_Generic(inst); + gpr.SpillLock(IRREG_LLBIT, inst.src3, inst.src1); + RiscVReg addrReg = INVALID_REG; + if (inst.src1 == MIPS_REG_ZERO) { + // This will get changed by AdjustForAddressOffset. + addrReg = MEMBASEREG; +#ifdef MASKED_PSP_MEMORY + inst.constant &= Memory::MEMVIEW32_MASK; +#endif + } else if ((jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) && inst.src3 != inst.src1) { + addrReg = gpr.MapRegAsPointer(inst.src1); + } else { + SetScratch1ToSrc1Address(inst.src1); + addrReg = SCRATCH1; + } + gpr.MapReg(inst.src3, inst.dest == MIPS_REG_ZERO ? MIPSMap::INIT : MIPSMap::DIRTY); + gpr.MapReg(IRREG_LLBIT); + gpr.ReleaseSpillLock(IRREG_LLBIT, inst.src3, inst.src1); + + s32 imm = AdjustForAddressOffset(&addrReg, inst.constant); + + // TODO: Safe memory? Or enough to have crash handler + validate? + + FixupBranch condFailed = BEQ(gpr.R(IRREG_LLBIT), R_ZERO); + SW(gpr.R(inst.src3), addrReg, imm); + + if (inst.dest != MIPS_REG_ZERO) { + LI(gpr.R(inst.dest), 1); + FixupBranch finish = J(); + + SetJumpTarget(condFailed); + LI(gpr.R(inst.dest), 0); + SetJumpTarget(finish); + } else { + SetJumpTarget(condFailed); + } } void RiscVJit::CompIR_StoreShift(IRInst inst) { diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index 639c11843a..25f7b5db59 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -20,6 +20,7 @@ #endif #include "Common/CPUDetect.h" +#include "Core/MIPS/IR/IRInst.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" #include "Core/MIPS/JitCommon/JitState.h" #include "Core/Reporting.h" @@ -998,7 +999,7 @@ bool RiscVRegCache::IsValidReg(IRRegIndex r) const { if (r >= 224 && r < 224 + 16) return false; // Don't allow nextPC, etc. since it's probably a mistake. - if (r > 245) + if (r > IRREG_FPCOND && r != IRREG_LLBIT) return false; // Don't allow PC either. if (r == 241) From c56f302e515dd77da91511ad3eb16d3f8cfd2f7d Mon Sep 17 00:00:00 2001 From: Hoe Hao Cheng Date: Sun, 30 Jul 2023 16:25:36 +0800 Subject: [PATCH 145/167] SDL: address comments on #17780 --- Common/Render/Text/draw_text_sdl.cpp | 14 ++++---------- Common/Render/Text/draw_text_sdl.h | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Common/Render/Text/draw_text_sdl.cpp b/Common/Render/Text/draw_text_sdl.cpp index 0de2b74397..701ecc64c8 100644 --- a/Common/Render/Text/draw_text_sdl.cpp +++ b/Common/Render/Text/draw_text_sdl.cpp @@ -58,7 +58,7 @@ void TextDrawerSDL::PrepareFallbackFonts() { }; for (int i = 0; i < ARRAY_SIZE(names); i++) { - FcFontSet *foundFonts = FcFontList(config, names[i], os); + FcFontSet *foundFonts = FcFontList(config, names[i], os); for (int j = 0; foundFonts && j < foundFonts->nfont; ++j) { FcPattern* font = foundFonts->fonts[j]; @@ -133,7 +133,7 @@ void TextDrawerSDL::PrepareFallbackFonts() { #endif } -uint32_t TextDrawerSDL::CheckMissingGlyph(std::string& text) { +uint32_t TextDrawerSDL::CheckMissingGlyph(const std::string& text) { TTF_Font *font = fontMap_.find(fontHash_)->second; UTF8 utf8Decoded(text.c_str()); @@ -293,7 +293,7 @@ void TextDrawerSDL::MeasureStringRect(const char *str, size_t len, const Bounds if (total_w < entry->width) { total_w = entry->width; } - total_h += entry->height; + total_h += TTF_FontLineSkip(font); } *w = total_w * fontScaleX_ * dpiScale_; @@ -359,13 +359,7 @@ void TextDrawerSDL::DrawStringBitmap(std::vector &bitmapData, TextStrin // If a string includes only newlines, SDL2_ttf will refuse to render it // thinking it is empty. Add a space to avoid that. - bool isAllNewline = true; - for (int i = 0; i < processedStr.size(); i++) { - if (processedStr[i] != '\n') { - isAllNewline = false; - break; - } - } + bool isAllNewline = processedStr.find_first_not_of('\n') == std::string::npos; if (isAllNewline) { processedStr.push_back(' '); diff --git a/Common/Render/Text/draw_text_sdl.h b/Common/Render/Text/draw_text_sdl.h index 7c047ee48c..87c543a660 100644 --- a/Common/Render/Text/draw_text_sdl.h +++ b/Common/Render/Text/draw_text_sdl.h @@ -29,7 +29,7 @@ public: protected: void ClearCache() override; void PrepareFallbackFonts(); - uint32_t CheckMissingGlyph(std::string& text); + uint32_t CheckMissingGlyph(const std::string& text); bool FindFallbackFonts(uint32_t missingGlyph, int ptSize); uint32_t fontHash_; From 6da6de8201329785bf045243d24f8c6eb9fdd0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 30 Jul 2023 11:13:42 +0200 Subject: [PATCH 146/167] Re-enable framebuffer fetch for blend where available. Accidentally disabled this in #17575 Helps #17797 but only on OpenGL on mobile. There's more to improve there. caps_.framebufferFetchSupported is now always set to false in Vulkan. --- GPU/GPUCommonHW.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index d9bcae2087..ffdf75c0d0 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -607,6 +607,10 @@ u32 GPUCommonHW::CheckGPUFeatures() const { features |= GPU_USE_VS_RANGE_CULLING; } + if (draw_->GetDeviceCaps().framebufferFetchSupported) { + features |= GPU_USE_FRAMEBUFFER_FETCH; + } + if (draw_->GetShaderLanguageDesc().bitwiseOps) { features |= GPU_USE_LIGHT_UBERSHADER; } From 1c05f71b50823dd058c8f652187574bb54265036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 30 Jul 2023 10:55:55 +0200 Subject: [PATCH 147/167] Update to the latest version of rcheevos. --- Common/System/OSD.cpp | 25 ++++++++++++++++++------- Common/System/OSD.h | 2 +- Core/RetroAchievements.cpp | 30 +++++++++++++++++++++--------- UI/RetroAchievementScreens.cpp | 10 +++++----- ext/rcheevos | 2 +- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/Common/System/OSD.cpp b/Common/System/OSD.cpp index 1b26acd89e..e8cf697b27 100644 --- a/Common/System/OSD.cpp +++ b/Common/System/OSD.cpp @@ -128,25 +128,36 @@ void OnScreenDisplay::ShowAchievementUnlocked(int achievementID) { entries_.insert(entries_.begin(), msg); } -void OnScreenDisplay::ShowAchievementProgress(int achievementID, float duration_s) { +void OnScreenDisplay::ShowAchievementProgress(int achievementID, bool show) { double now = time_now_d(); + // There can only be one of these at a time. for (auto &entry : sideEntries_) { - if (entry.numericID == achievementID && entry.type == OSDType::ACHIEVEMENT_PROGRESS) { - // Duplicate, let's just bump the timer. - entry.startTime = now; - entry.endTime = now + (double)duration_s; - // We're done. + if (entry.type == OSDType::ACHIEVEMENT_PROGRESS) { + if (!show) { + // Hide and eventually delete it. + entry.endTime = now + (double)FadeoutTime(); + // Found it, we're done. + return; + } + // Else update it. + entry.numericID = achievementID; + entry.endTime = now + forever_s; return; } } + if (!show) { + // Sanity check + return; + } + // OK, let's make a new side-entry. Entry entry; entry.numericID = achievementID; entry.type = OSDType::ACHIEVEMENT_PROGRESS; entry.startTime = now; - entry.endTime = now + (double)duration_s; + entry.endTime = now + forever_s; sideEntries_.insert(sideEntries_.begin(), entry); } diff --git a/Common/System/OSD.h b/Common/System/OSD.h index 823236469d..cecf0cae79 100644 --- a/Common/System/OSD.h +++ b/Common/System/OSD.h @@ -44,7 +44,7 @@ public: // Specialized achievement-related types. These go to the side notifications, not the top-middle. void ShowAchievementUnlocked(int achievementID); - void ShowAchievementProgress(int achievementID, float duration_s); + void ShowAchievementProgress(int achievementID, bool show); // call with show=false to hide. There can only be one of these. When hiding it's ok to not pass a valid achievementID. void ShowChallengeIndicator(int achievementID, bool show); // call with show=false to hide. void ShowLeaderboardTracker(int leaderboardTrackerID, const char *trackerText, bool show); // show=true is used both for create and update. diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index 4c3ea97710..22b3c3bcee 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -249,40 +249,52 @@ static void event_handler_callback(const rc_client_event_t *event, rc_client_t * g_OSD.Show(OSDType::MESSAGE_INFO, ApplySafeSubstitutions(ac->T("%1: Leaderboard attempt started"), event->leaderboard->title), DeNull(event->leaderboard->description), 3.0f); break; case RC_CLIENT_EVENT_LEADERBOARD_FAILED: - NOTICE_LOG(ACHIEVEMENTS, "Leaderboard attempt failed: %s", event->leaderboard->title); + INFO_LOG(ACHIEVEMENTS, "Leaderboard attempt failed: %s", event->leaderboard->title); g_OSD.Show(OSDType::MESSAGE_INFO, ApplySafeSubstitutions(ac->T("%1: Leaderboard attempt failed"), event->leaderboard->title), 3.0f); // A leaderboard attempt has failed. break; case RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED: - NOTICE_LOG(ACHIEVEMENTS, "Leaderboard result submitted: %s", event->leaderboard->title); + INFO_LOG(ACHIEVEMENTS, "Leaderboard result submitted: %s", event->leaderboard->title); g_OSD.Show(OSDType::MESSAGE_SUCCESS, ApplySafeSubstitutions(ac->T("Submitted %1 for %2"), DeNull(event->leaderboard->tracker_value), DeNull(event->leaderboard->title)), DeNull(event->leaderboard->description), 3.0f); System_PostUIMessage("play_sound", "leaderboard_submitted"); break; case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW: - NOTICE_LOG(ACHIEVEMENTS, "Challenge indicator show: %s", event->achievement->title); + INFO_LOG(ACHIEVEMENTS, "Challenge indicator show: %s", event->achievement->title); g_OSD.ShowChallengeIndicator(event->achievement->id, true); g_activeChallenges.insert(event->achievement->id); // A challenge achievement has become active. The handler should show a small version of the achievement icon // to indicate the challenge is active. break; case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE: - NOTICE_LOG(ACHIEVEMENTS, "Challenge indicator hide: %s", event->achievement->title); + INFO_LOG(ACHIEVEMENTS, "Challenge indicator hide: %s", event->achievement->title); g_OSD.ShowChallengeIndicator(event->achievement->id, false); g_activeChallenges.erase(event->achievement->id); // The handler should hide the small version of the achievement icon that was shown by the corresponding RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW event. break; case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW: - NOTICE_LOG(ACHIEVEMENTS, "Progress indicator show: %s, progress: '%s' (%f)", event->achievement->title, event->achievement->measured_progress, event->achievement->measured_percent); + INFO_LOG(ACHIEVEMENTS, "Progress indicator show: %s, progress: '%s' (%f)", event->achievement->title, event->achievement->measured_progress, event->achievement->measured_percent); // An achievement that tracks progress has changed the amount of progress that has been made. // The handler should show a small version of the achievement icon along with the achievement->measured_progress text (for two seconds). // Only one progress indicator should be shown at a time. // If a progress indicator is already visible, it should be updated with the new icon and text, and the two second timer should be restarted. - g_OSD.ShowAchievementProgress(event->achievement->id, 2.0f); + g_OSD.ShowAchievementProgress(event->achievement->id, true); + break; + case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE: + INFO_LOG(ACHIEVEMENTS, "Progress indicator update: %s, progress: '%s' (%f)", event->achievement->title, event->achievement->measured_progress, event->achievement->measured_percent); + g_OSD.ShowAchievementProgress(event->achievement->id, true); + break; + case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE: + INFO_LOG(ACHIEVEMENTS, "Progress indicator hide"); + // An achievement that tracks progress has changed the amount of progress that has been made. + // The handler should show a small version of the achievement icon along with the achievement->measured_progress text (for two seconds). + // Only one progress indicator should be shown at a time. + // If a progress indicator is already visible, it should be updated with the new icon and text, and the two second timer should be restarted. + g_OSD.ShowAchievementProgress(0, false); break; case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW: - NOTICE_LOG(ACHIEVEMENTS, "Leaderboard tracker show: '%s' (id %d)", event->leaderboard_tracker->display, event->leaderboard_tracker->id); + INFO_LOG(ACHIEVEMENTS, "Leaderboard tracker show: '%s' (id %d)", event->leaderboard_tracker->display, event->leaderboard_tracker->id); // A leaderboard_tracker has become active. The handler should show the tracker text on screen. // Multiple active leaderboards may share a single tracker if they have the same definition and value. // As such, the leaderboard tracker IDs are unique amongst the leaderboard trackers, and have no correlation to the active leaderboard(s). @@ -291,12 +303,12 @@ static void event_handler_callback(const rc_client_event_t *event, rc_client_t * break; case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE: // A leaderboard_tracker has become inactive. The handler should hide the tracker text from the screen. - NOTICE_LOG(ACHIEVEMENTS, "Leaderboard tracker hide: '%s' (id %d)", event->leaderboard_tracker->display, event->leaderboard_tracker->id); + INFO_LOG(ACHIEVEMENTS, "Leaderboard tracker hide: '%s' (id %d)", event->leaderboard_tracker->display, event->leaderboard_tracker->id); g_OSD.ShowLeaderboardTracker(event->leaderboard_tracker->id, nullptr, false); break; case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE: // A leaderboard_tracker value has been updated. The handler should update the tracker text on the screen. - NOTICE_LOG(ACHIEVEMENTS, "Leaderboard tracker update: '%s' (id %d)", event->leaderboard_tracker->display, event->leaderboard_tracker->id); + INFO_LOG(ACHIEVEMENTS, "Leaderboard tracker update: '%s' (id %d)", event->leaderboard_tracker->display, event->leaderboard_tracker->id); g_OSD.ShowLeaderboardTracker(event->leaderboard_tracker->id, event->leaderboard_tracker->display, true); break; case RC_CLIENT_EVENT_RESET: diff --git a/UI/RetroAchievementScreens.cpp b/UI/RetroAchievementScreens.cpp index 8ab19214ed..8bc67c8d44 100644 --- a/UI/RetroAchievementScreens.cpp +++ b/UI/RetroAchievementScreens.cpp @@ -675,14 +675,14 @@ void AchievementView::Click() { #ifdef _DEBUG static int type = 0; type++; - type = type % 4; + type = type % 5; switch (type) { case 0: g_OSD.ShowAchievementUnlocked(achievement_->id); break; - case 1: g_OSD.ShowAchievementProgress(achievement_->id, 2.0f); break; - case 2: g_OSD.ShowChallengeIndicator(achievement_->id, true); break; - case 3: g_OSD.ShowChallengeIndicator(achievement_->id, false); break; + case 1: g_OSD.ShowAchievementProgress(achievement_->id, true); break; + case 2: g_OSD.ShowAchievementProgress(achievement_->id, false); break; + case 3: g_OSD.ShowChallengeIndicator(achievement_->id, true); break; + case 4: g_OSD.ShowChallengeIndicator(achievement_->id, false); break; } - #endif } diff --git a/ext/rcheevos b/ext/rcheevos index 97db619aa4..13333347f0 160000 --- a/ext/rcheevos +++ b/ext/rcheevos @@ -1 +1 @@ -Subproject commit 97db619aa4de15fe19887255e75899ae65d2e432 +Subproject commit 13333347f0809aa751856c849ffe751d612ae0a4 From 061131ec8aed90d11c14561aac0223d64c9b463e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 30 Jul 2023 14:42:22 +0200 Subject: [PATCH 148/167] Cache planes used for BBOX culling This isn't a huge performance boost for the games that use BBOX (like Tekken), but it'll be more valuable if we start using soft culling more widely automatically, see #17808 --- GPU/Common/DrawEngineCommon.cpp | 125 ++++++++++++++++---------------- GPU/Common/DrawEngineCommon.h | 14 ++++ GPU/Common/ShaderCommon.h | 3 +- GPU/GPU.h | 9 ++- GPU/GPUCommon.cpp | 1 + GPU/GPUCommonHW.cpp | 38 +++++----- 6 files changed, 106 insertions(+), 84 deletions(-) diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index bd04fa13f1..de00bf5d6a 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -19,9 +19,9 @@ #include #include "Common/Data/Convert/ColorConv.h" -#include "Common/Math/lin/matrix4x4.h" #include "Common/Profiler/Profiler.h" #include "Common/LogReporting.h" +#include "Common/Math/lin/matrix4x4.h" #include "Core/Config.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/SplineCommon.h" @@ -136,21 +136,6 @@ std::string DrawEngineCommon::DebugGetVertexLoaderString(std::string id, DebugSh return dec ? dec->GetString(stringType) : "N/A"; } -struct Plane { - float x, y, z, w; - void Set(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; } - float Test(const float f[3]) const { return x * f[0] + y * f[1] + z * f[2] + w; } -}; - -static void PlanesFromMatrix(const float mtx[16], Plane planes[6]) { - planes[0].Set(mtx[3]-mtx[0], mtx[7]-mtx[4], mtx[11]-mtx[8], mtx[15]-mtx[12]); // Right - planes[1].Set(mtx[3]+mtx[0], mtx[7]+mtx[4], mtx[11]+mtx[8], mtx[15]+mtx[12]); // Left - planes[2].Set(mtx[3]+mtx[1], mtx[7]+mtx[5], mtx[11]+mtx[9], mtx[15]+mtx[13]); // Bottom - planes[3].Set(mtx[3]-mtx[1], mtx[7]-mtx[5], mtx[11]-mtx[9], mtx[15]-mtx[13]); // Top - planes[4].Set(mtx[3]+mtx[2], mtx[7]+mtx[6], mtx[11]+mtx[10], mtx[15]+mtx[14]); // Near - planes[5].Set(mtx[3]-mtx[2], mtx[7]-mtx[6], mtx[11]-mtx[10], mtx[15]-mtx[14]); // Far -} - static Vec3f ClipToScreen(const Vec4f& coords) { float xScale = gstate.getViewportXScale(); float xCenter = gstate.getViewportXCenter(); @@ -250,6 +235,52 @@ void DrawEngineCommon::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex } } +// Gated by DIRTY_CULL_PLANES +void DrawEngineCommon::UpdatePlanes() { + float world[16]; + float view[16]; + float worldview[16]; + float worldviewproj[16]; + ConvertMatrix4x3To4x4(world, gstate.worldMatrix); + ConvertMatrix4x3To4x4(view, gstate.viewMatrix); + // TODO: Create a Matrix4x3ByMatrix4x3, and Matrix4x4ByMatrix4x3? + Matrix4ByMatrix4(worldview, world, view); + Matrix4ByMatrix4(worldviewproj, worldview, gstate.projMatrix); + + // Next, we need to apply viewport, scissor, region, and even offset - but only for X/Y. + // Note that the PSP does not clip against the viewport. + const Vec2f baseOffset = Vec2f(gstate.getOffsetX(), gstate.getOffsetY()); + // Region1 (rate) is used as an X1/Y1 here, matching PSP behavior. + minOffset_ = baseOffset + Vec2f(std::max(gstate.getRegionRateX() - 0x100, gstate.getScissorX1()), std::max(gstate.getRegionRateY() - 0x100, gstate.getScissorY1())) - Vec2f(1.0f, 1.0f); + maxOffset_ = baseOffset + Vec2f(std::min(gstate.getRegionX2(), gstate.getScissorX2()), std::min(gstate.getRegionY2(), gstate.getScissorY2())) + Vec2f(1.0f, 1.0f); + + // Now let's apply the viewport to our scissor/region + offset range. + Vec2f inverseViewportScale = Vec2f(1.0f / gstate.getViewportXScale(), 1.0f / gstate.getViewportYScale()); + Vec2f minViewport = (minOffset_ - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale; + Vec2f maxViewport = (maxOffset_ - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale; + + Lin::Matrix4x4 applyViewport; + applyViewport.empty(); + // Scale to the viewport's size. + applyViewport.xx = 2.0f / (maxViewport.x - minViewport.x); + applyViewport.yy = 2.0f / (maxViewport.y - minViewport.y); + applyViewport.zz = 1.0f; + applyViewport.ww = 1.0f; + // And offset to the viewport's centers. + applyViewport.wx = -(maxViewport.x + minViewport.x) / (maxViewport.x - minViewport.x); + applyViewport.wy = -(maxViewport.y + minViewport.y) / (maxViewport.y - minViewport.y); + + float mtx[16]; + Matrix4ByMatrix4(mtx, worldviewproj, applyViewport.m); + + planes_[0].Set(mtx[3] - mtx[0], mtx[7] - mtx[4], mtx[11] - mtx[8], mtx[15] - mtx[12]); // Right + planes_[1].Set(mtx[3] + mtx[0], mtx[7] + mtx[4], mtx[11] + mtx[8], mtx[15] + mtx[12]); // Left + planes_[2].Set(mtx[3] + mtx[1], mtx[7] + mtx[5], mtx[11] + mtx[9], mtx[15] + mtx[13]); // Bottom + planes_[3].Set(mtx[3] - mtx[1], mtx[7] - mtx[5], mtx[11] - mtx[9], mtx[15] - mtx[13]); // Top + planes_[4].Set(mtx[3] + mtx[2], mtx[7] + mtx[6], mtx[11] + mtx[10], mtx[15] + mtx[14]); // Near + planes_[5].Set(mtx[3] - mtx[2], mtx[7] - mtx[6], mtx[11] - mtx[10], mtx[15] - mtx[14]); // Far +} + // This code has plenty of potential for optimization. // // It does the simplest and safest test possible: If all points of a bbox is outside a single of @@ -273,7 +304,7 @@ bool DrawEngineCommon::TestBoundingBox(const void *control_points, const void *i verts[i] = vtx[i] * (1.0f / 128.0f); } } else if ((vertType & 0xFFFFFF) == GE_VTYPE_POS_16BIT && !inds) { - const s16 *vtx = (const s16*)control_points; + const s16 *vtx = (const s16 *)control_points; for (int i = 0; i < vertexCount * 3; i++) { verts[i] = vtx[i] * (1.0f / 32768.0f); } @@ -302,70 +333,42 @@ bool DrawEngineCommon::TestBoundingBox(const void *control_points, const void *i } } - Plane planes[6]; + // Due to world matrix updates per "thing", this isn't quite as effective as it could be if we did world transform + // in here as well. Though, it still does cut down on a lot of updates in Tekken 6. + if (gstate_c.IsDirty(DIRTY_CULL_PLANES)) { + UpdatePlanes(); + gpuStats.numPlaneUpdates++; + gstate_c.Clean(DIRTY_CULL_PLANES); + } - float world[16]; - float view[16]; - float worldview[16]; - float worldviewproj[16]; - ConvertMatrix4x3To4x4(world, gstate.worldMatrix); - ConvertMatrix4x3To4x4(view, gstate.viewMatrix); - // TODO: Create a Matrix4x3ByMatrix4x3, and Matrix4x4ByMatrix4x3? - Matrix4ByMatrix4(worldview, world, view); - Matrix4ByMatrix4(worldviewproj, worldview, gstate.projMatrix); - - // Next, we need to apply viewport, scissor, region, and even offset - but only for X/Y. - // Note that the PSP does not clip against the viewport. - const Vec2f baseOffset = Vec2f(gstate.getOffsetX(), gstate.getOffsetY()); - // Region1 (rate) is used as an X1/Y1 here, matching PSP behavior. - Vec2f minOffset = baseOffset + Vec2f(std::max(gstate.getRegionRateX() - 0x100, gstate.getScissorX1()), std::max(gstate.getRegionRateY() - 0x100, gstate.getScissorY1())) - Vec2f(1.0f, 1.0f); - Vec2f maxOffset = baseOffset + Vec2f(std::min(gstate.getRegionX2(), gstate.getScissorX2()), std::min(gstate.getRegionY2(), gstate.getScissorY2())) + Vec2f(1.0f, 1.0f); - - // Now let's apply the viewport to our scissor/region + offset range. - Vec2f inverseViewportScale = Vec2f(1.0f / gstate.getViewportXScale(), 1.0f / gstate.getViewportYScale()); - Vec2f minViewport = (minOffset - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale; - Vec2f maxViewport = (maxOffset - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale; - - Lin::Matrix4x4 applyViewport; - applyViewport.empty(); - // Scale to the viewport's size. - applyViewport.xx = 2.0f / (maxViewport.x - minViewport.x); - applyViewport.yy = 2.0f / (maxViewport.y - minViewport.y); - applyViewport.zz = 1.0f; - applyViewport.ww = 1.0f; - // And offset to the viewport's centers. - applyViewport.wx = -(maxViewport.x + minViewport.x) / (maxViewport.x - minViewport.x); - applyViewport.wy = -(maxViewport.y + minViewport.y) / (maxViewport.y - minViewport.y); - - float screenBounds[16]; - Matrix4ByMatrix4(screenBounds, worldviewproj, applyViewport.m); - - PlanesFromMatrix(screenBounds, planes); // Note: near/far are not checked without clamp/clip enabled, so we skip those planes. int totalPlanes = gstate.isDepthClampEnabled() ? 6 : 4; for (int plane = 0; plane < totalPlanes; plane++) { int inside = 0; int out = 0; for (int i = 0; i < vertexCount; i++) { - // Here we can test against the frustum planes! - float value = planes[plane].Test(verts + i * 3); + // Test against the frustum planes, and count. + // TODO: We should test 4 vertices at a time using SIMD. + // I guess could also test one vertex against 4 planes at a time, though a lot of waste at the common case of 6. + float value = planes_[plane].Test(verts + i * 3); if (value <= -FLT_EPSILON) out++; else inside++; } + // No vertices inside this one plane? Don't need to draw. if (inside == 0) { // All out - but check for X and Y if the offset was near the cullbox edge. bool outsideEdge = false; if (plane == 1) - outsideEdge = minOffset.x < 1.0f; + outsideEdge = minOffset_.x < 1.0f; if (plane == 2) - outsideEdge = minOffset.y < 1.0f; + outsideEdge = minOffset_.y < 1.0f; else if (plane == 0) - outsideEdge = maxOffset.x >= 4096.0f; + outsideEdge = maxOffset_.x >= 4096.0f; else if (plane == 3) - outsideEdge = maxOffset.y >= 4096.0f; + outsideEdge = maxOffset_.y >= 4096.0f; // Only consider this outside if offset + scissor/region is fully inside the cullbox. if (!outsideEdge) diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index b9be2ee2e4..beb3e27757 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -22,6 +22,7 @@ #include "Common/CommonTypes.h" #include "Common/Data/Collections/Hashmaps.h" +#include "GPU/Math3D.h" #include "GPU/GPUState.h" #include "GPU/Common/GPUStateUtils.h" #include "GPU/Common/GPUDebugInterface.h" @@ -68,6 +69,13 @@ public: virtual void SendDataToShader(const SimpleVertex *const *points, int size_u, int size_v, u32 vertType, const Spline::Weight2D &weights) = 0; }; +// Culling plane. +struct Plane { + float x, y, z, w; + void Set(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; } + float Test(const float f[3]) const { return x * f[0] + y * f[1] + z * f[2] + w; } +}; + class DrawEngineCommon { public: DrawEngineCommon(); @@ -131,6 +139,7 @@ public: protected: virtual bool UpdateUseHWTessellation(bool enabled) const { return enabled; } + void UpdatePlanes(); int ComputeNumVertsToDecode() const; void DecodeVerts(u8 *dest); @@ -236,4 +245,9 @@ protected: // Hardware tessellation TessellationDataTransfer *tessDataTransfer; + + // Culling + Plane planes_[6]; + Vec2f minOffset_; + Vec2f maxOffset_; }; diff --git a/GPU/Common/ShaderCommon.h b/GPU/Common/ShaderCommon.h index 5130242240..95cf3908f2 100644 --- a/GPU/Common/ShaderCommon.h +++ b/GPU/Common/ShaderCommon.h @@ -93,7 +93,7 @@ enum : uint64_t { DIRTY_LIGHT_CONTROL = 1ULL << 38, DIRTY_TEX_ALPHA_MUL = 1ULL << 39, - // Bits 40-43 are free for new uniforms. Then we're really out and need to start merging. + // Bits 40-42 are free for new uniforms. Then we're really out and need to start merging. // Don't forget to update DIRTY_ALL_UNIFORMS when you start using them. DIRTY_BONE_UNIFORMS = 0xFF000000ULL, @@ -101,6 +101,7 @@ enum : uint64_t { DIRTY_ALL_UNIFORMS = 0x0FFFFFFFFFFULL, // Other dirty elements that aren't uniforms + DIRTY_CULL_PLANES = 1ULL << 43, DIRTY_FRAMEBUF = 1ULL << 44, DIRTY_TEXTURE_IMAGE = 1ULL << 45, // Means that the definition of the texture image has changed (address, stride etc), and we need to look up again. DIRTY_TEXTURE_PARAMS = 1ULL << 46, diff --git a/GPU/GPU.h b/GPU/GPU.h index ba5ada12e8..9e9dd6049a 100644 --- a/GPU/GPU.h +++ b/GPU/GPU.h @@ -66,6 +66,7 @@ inline unsigned int toFloat24(float f) { return i >> 8; } +// The ToString function lives in GPUCommonHW.cpp. struct GPUStatistics { void Reset() { ResetFrame(); @@ -84,10 +85,10 @@ struct GPUStatistics { numTextureInvalidations = 0; numTextureInvalidationsByFramebuffer = 0; numTexturesHashed = 0; - numTextureSwitches = 0; numTextureDataBytesHashed = 0; - numShaderSwitches = 0; numFlushes = 0; + numBBOXJumps = 0; + numPlaneUpdates = 0; numTexturesDecoded = 0; numFramebufferEvaluations = 0; numBlockingReadbacks = 0; @@ -114,6 +115,8 @@ struct GPUStatistics { int numListSyncs; int numCachedDrawCalls; int numFlushes; + int numBBOXJumps; + int numPlaneUpdates; int numVertsSubmitted; int numCachedVertsDrawn; int numUncachedVertsDrawn; @@ -122,8 +125,6 @@ struct GPUStatistics { int numTextureInvalidationsByFramebuffer; int numTexturesHashed; int numTextureDataBytesHashed; - int numTextureSwitches; - int numShaderSwitches; int numTexturesDecoded; int numFramebufferEvaluations; int numBlockingReadbacks; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index c5257f2413..c92a233a8b 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -931,6 +931,7 @@ void GPUCommon::Execute_BJump(u32 op, u32 diff) { if (!currentList->bboxResult) { // bounding box jump. const u32 target = gstate_c.getRelativeAddress(op & 0x00FFFFFC); + gpuStats.numBBOXJumps++; if (Memory::IsValidAddress(target)) { UpdatePC(currentList->pc, target - 4); currentList->pc = target - 4; // pc will be increased after we return, counteract that diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index ffdf75c0d0..701cfb1651 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -191,12 +191,12 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_ANTIALIASENABLE, FLAG_FLUSHBEFOREONCHANGE }, // Viewport. - { GE_CMD_OFFSETX, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - { GE_CMD_OFFSETY, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - { GE_CMD_VIEWPORTXSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, - { GE_CMD_VIEWPORTYSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, - { GE_CMD_VIEWPORTXCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, - { GE_CMD_VIEWPORTYCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, + { GE_CMD_OFFSETX, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_CULL_PLANES }, + { GE_CMD_OFFSETY, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_CULL_PLANES }, + { GE_CMD_VIEWPORTXSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULL_PLANES }, + { GE_CMD_VIEWPORTYSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULL_PLANES }, + { GE_CMD_VIEWPORTXCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULL_PLANES }, + { GE_CMD_VIEWPORTYCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULL_PLANES }, { GE_CMD_VIEWPORTZSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_DEPTHRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, { GE_CMD_VIEWPORTZCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_DEPTHRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, { GE_CMD_DEPTHCLAMPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_RASTER_STATE }, @@ -206,12 +206,12 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_MAXZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHRANGE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, // Region - { GE_CMD_REGION1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - { GE_CMD_REGION2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, + { GE_CMD_REGION1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_CULL_PLANES }, + { GE_CMD_REGION2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_CULL_PLANES }, // Scissor - { GE_CMD_SCISSOR1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - { GE_CMD_SCISSOR2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, + { GE_CMD_SCISSOR1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_CULL_PLANES }, + { GE_CMD_SCISSOR2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_CULL_PLANES }, // Lighting base colors { GE_CMD_AMBIENTCOLOR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_AMBIENT }, @@ -1368,7 +1368,7 @@ void GPUCommonHW::Execute_WorldMtxNum(u32 op, u32 diff) { if (dst[i] != newVal) { Flush(); dst[i] = newVal; - gstate_c.Dirty(DIRTY_WORLDMATRIX); + gstate_c.Dirty(DIRTY_WORLDMATRIX | DIRTY_CULL_PLANES); } if (++i >= end) { break; @@ -1391,7 +1391,7 @@ void GPUCommonHW::Execute_WorldMtxData(u32 op, u32 diff) { if (num < 12 && newVal != ((const u32 *)gstate.worldMatrix)[num]) { Flush(); ((u32 *)gstate.worldMatrix)[num] = newVal; - gstate_c.Dirty(DIRTY_WORLDMATRIX); + gstate_c.Dirty(DIRTY_WORLDMATRIX | DIRTY_CULL_PLANES); } num++; gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (num & 0x00FFFFFF); @@ -1421,7 +1421,7 @@ void GPUCommonHW::Execute_ViewMtxNum(u32 op, u32 diff) { if (dst[i] != newVal) { Flush(); dst[i] = newVal; - gstate_c.Dirty(DIRTY_VIEWMATRIX); + gstate_c.Dirty(DIRTY_VIEWMATRIX | DIRTY_CULL_PLANES); } if (++i >= end) { break; @@ -1444,7 +1444,7 @@ void GPUCommonHW::Execute_ViewMtxData(u32 op, u32 diff) { if (num < 12 && newVal != ((const u32 *)gstate.viewMatrix)[num]) { Flush(); ((u32 *)gstate.viewMatrix)[num] = newVal; - gstate_c.Dirty(DIRTY_VIEWMATRIX); + gstate_c.Dirty(DIRTY_VIEWMATRIX | DIRTY_CULL_PLANES); } num++; gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (num & 0x00FFFFFF); @@ -1474,7 +1474,7 @@ void GPUCommonHW::Execute_ProjMtxNum(u32 op, u32 diff) { if (dst[i] != newVal) { Flush(); dst[i] = newVal; - gstate_c.Dirty(DIRTY_PROJMATRIX); + gstate_c.Dirty(DIRTY_PROJMATRIX | DIRTY_CULL_PLANES); } if (++i >= end) { break; @@ -1497,7 +1497,7 @@ void GPUCommonHW::Execute_ProjMtxData(u32 op, u32 diff) { if (num < 16 && newVal != ((const u32 *)gstate.projMatrix)[num]) { Flush(); ((u32 *)gstate.projMatrix)[num] = newVal; - gstate_c.Dirty(DIRTY_PROJMATRIX); + gstate_c.Dirty(DIRTY_PROJMATRIX | DIRTY_CULL_PLANES); } num++; if (num <= 16) @@ -1647,8 +1647,8 @@ size_t GPUCommonHW::FormatGPUStatsCommon(char *buffer, size_t size) { float vertexAverageCycles = gpuStats.numVertsSubmitted > 0 ? (float)gpuStats.vertexGPUCycles / (float)gpuStats.numVertsSubmitted : 0.0f; return snprintf(buffer, size, "DL processing time: %0.2f ms, %d drawsync, %d listsync\n" - "Draw calls: %d, flushes %d, clears %d (cached: %d)\n" - "Num Tracked Vertex Arrays: %d\n" + "Draw calls: %d, flushes %d, clears %d, bbox jumps %d (%d updates)\n" + "Cached draws: %d (tracked: %d)\n" "Vertices: %d cached: %d uncached: %d\n" "FBOs active: %d (evaluations: %d)\n" "Textures: %d, dec: %d, invalidated: %d, hashed: %d kB\n" @@ -1662,6 +1662,8 @@ size_t GPUCommonHW::FormatGPUStatsCommon(char *buffer, size_t size) { gpuStats.numDrawCalls, gpuStats.numFlushes, gpuStats.numClears, + gpuStats.numBBOXJumps, + gpuStats.numPlaneUpdates, gpuStats.numCachedDrawCalls, gpuStats.numTrackedVertexArrays, gpuStats.numVertsSubmitted, From fd656c629daeb5095fb87000d7a5995250dc1e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 30 Jul 2023 17:45:19 +0200 Subject: [PATCH 149/167] More dirtying --- GPU/GPUState.cpp | 7 +++++++ GPU/Software/SoftGpu.cpp | 3 +++ 2 files changed, 10 insertions(+) diff --git a/GPU/GPUState.cpp b/GPU/GPUState.cpp index 28c13b871a..c5f9882202 100644 --- a/GPU/GPUState.cpp +++ b/GPU/GPUState.cpp @@ -128,6 +128,8 @@ void GPUgstate::Reset() { memset(gstate.boneMatrix, 0, sizeof(gstate.boneMatrix)); savedContextVersion = 1; + + gstate_c.Dirty(DIRTY_CULL_PLANES); } void GPUgstate::Save(u32_le *ptr) { @@ -258,6 +260,8 @@ void GPUgstate::Restore(const u32_le *ptr) { if (gpu) gpu->ResetMatrices(); + + gstate_c.Dirty(DIRTY_CULL_PLANES); } bool vertTypeIsSkinningEnabled(u32 vertType) { @@ -366,6 +370,9 @@ void GPUStateCache::DoState(PointerWrap &p) { } else { Do(p, savedContextVersion); } + + if (p.GetMode() == PointerWrap::MODE_READ) + gstate_c.Dirty(DIRTY_CULL_PLANES); } static const char *const gpuUseFlagNames[32] = { diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 5f708a2df0..c02b0ed9f9 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -1081,6 +1081,7 @@ void SoftGPU::Execute_WorldMtxData(u32 op, u32 diff) { if (newVal != *target) { *target = newVal; dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX; + gstate_c.Dirty(DIRTY_CULL_PLANES); } } @@ -1101,6 +1102,7 @@ void SoftGPU::Execute_ViewMtxData(u32 op, u32 diff) { if (newVal != *target) { *target = newVal; dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX; + gstate_c.Dirty(DIRTY_CULL_PLANES); } } @@ -1121,6 +1123,7 @@ void SoftGPU::Execute_ProjMtxData(u32 op, u32 diff) { if (newVal != *target) { *target = newVal; dirtyFlags_ |= SoftDirty::TRANSFORM_MATRIX; + gstate_c.Dirty(DIRTY_CULL_PLANES); } } From f0fd9e85aa9e0c593169fe7e4cdbdc1908fd187d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 30 Jul 2023 18:35:18 +0200 Subject: [PATCH 150/167] Try dirtying CULL_PLANES in Execute_BoundingBox in SoftGPU --- Core/HLE/sceDisplay.cpp | 2 +- GPU/Software/SoftGpu.cpp | 7 ++++++- GPU/Software/SoftGpu.h | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 19a1224bc0..70fbc0796f 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -594,7 +594,7 @@ void __DisplayFlip(int cyclesLate) { #ifndef _DEBUG auto err = GetI18NCategory(I18NCat::ERRORS); if (g_Config.bSoftwareRendering) { - g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: Try turning off Software Rendering")); + g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: Try turning off Software Rendering"), 5.0f); } else { g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: try frameskip, sound is choppy when slow")); } diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index c02b0ed9f9..b385680ebe 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -81,7 +81,7 @@ const SoftwareCommandTableEntry softgpuCommandTable[] = { { GE_CMD_VADDR, FLAG_EXECUTE, SoftDirty::NONE, &GPUCommon::Execute_Vaddr }, { GE_CMD_IADDR, FLAG_EXECUTE, SoftDirty::NONE, &GPUCommon::Execute_Iaddr }, { GE_CMD_BJUMP, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, SoftDirty::NONE, &GPUCommon::Execute_BJump }, - { GE_CMD_BOUNDINGBOX, FLAG_EXECUTE, SoftDirty::NONE, &GPUCommon::Execute_BoundingBox }, + { GE_CMD_BOUNDINGBOX, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_BoundingBox }, { GE_CMD_PRIM, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_Prim }, { GE_CMD_BEZIER, FLAG_EXECUTE, SoftDirty::NONE, &SoftGPU::Execute_Bezier }, @@ -1031,6 +1031,11 @@ void SoftGPU::Execute_FramebufFormat(u32 op, u32 diff) { drawEngine_->transformUnit.Flush("framebuf"); } +void SoftGPU::Execute_BoundingBox(u32 op, u32 diff) { + gstate_c.Dirty(DIRTY_CULL_PLANES); + GPUCommon::Execute_BoundingBox(op, diff); +} + void SoftGPU::Execute_ZbufPtr(u32 op, u32 diff) { // We assume depthbuf.data won't change while we're drawing. if (diff) { diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 8171c0e6b3..a5c9a17301 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -187,6 +187,9 @@ public: // Overridden to change flushing behavior. void Execute_Call(u32 op, u32 diff); + // Overridden for a dirty flag change. + void Execute_BoundingBox(u32 op, u32 diff); + void Execute_WorldMtxNum(u32 op, u32 diff); void Execute_ViewMtxNum(u32 op, u32 diff); void Execute_ProjMtxNum(u32 op, u32 diff); From 5db6b11ef286ca9d1a204f5327b4cf25ddab33f2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 10:19:40 -0700 Subject: [PATCH 151/167] irjit: Cleanup self-fmovs. These were sometimes getting emitted. --- Core/MIPS/IR/IRCompFPU.cpp | 3 ++- Core/MIPS/IR/IRCompVFPU.cpp | 8 +++++--- Core/MIPS/IR/IRPassSimplify.cpp | 4 +++- Core/MIPS/RiscV/RiscVCompFPU.cpp | 6 ++++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Core/MIPS/IR/IRCompFPU.cpp b/Core/MIPS/IR/IRCompFPU.cpp index 10a8d156bc..b0edeea0cf 100644 --- a/Core/MIPS/IR/IRCompFPU.cpp +++ b/Core/MIPS/IR/IRCompFPU.cpp @@ -152,7 +152,8 @@ void IRFrontend::Comp_FPU2op(MIPSOpcode op) { ir.Write(IROp::FAbs, fd, fs); break; case 6: //F(fd) = F(fs); break; //mov - ir.Write(IROp::FMov, fd, fs); + if (fd != fs) + ir.Write(IROp::FMov, fd, fs); break; case 7: //F(fd) = -F(fs); break; //neg ir.Write(IROp::FNeg, fd, fs); diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index 1c767d8e3d..c902951d96 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -238,7 +238,7 @@ namespace MIPSComp { } else { if (negate) ir.Write(IROp::FNeg, vregs[i], origV[regnum]); - else + else if (vregs[i] != origV[regnum]) ir.Write(IROp::FMov, vregs[i], origV[regnum]); } } else { @@ -855,7 +855,8 @@ namespace MIPSComp { switch (optype) { case 0: // d[i] = s[i]; break; //vmov // Probably for swizzle. - ir.Write(IROp::FMov, tempregs[i], sregs[i]); + if (tempregs[i] != sregs[i]) + ir.Write(IROp::FMov, tempregs[i], sregs[i]); break; case 1: // d[i] = fabsf(s[i]); break; //vabs ir.Write(IROp::FAbs, tempregs[i], sregs[i]); @@ -1171,7 +1172,8 @@ namespace MIPSComp { } for (int a = 0; a < n; a++) { for (int b = 0; b < n; b++) { - ir.Write(IROp::FMov, dregs[a * 4 + b], sregs[a * 4 + b]); + if (dregs[a * 4 + b] != sregs[a * 4 + b]) + ir.Write(IROp::FMov, dregs[a * 4 + b], sregs[a * 4 + b]); } } } diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 11b8e5292c..4e0bd372f4 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -128,7 +128,9 @@ bool OptimizeFPMoves(const IRWriter &in, IRWriter &out, const IROptions &opts) { if (prev.op == IROp::FMovToGPR && prev.dest == inst.src1) { inst.op = IROp::FMov; inst.src1 = prev.src1; - out.Write(inst); + // Skip it entirely if it's just a copy to and back. + if (inst.dest != inst.src1) + out.Write(inst); } else { out.Write(inst); } diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index ead8244712..154a7cfbf5 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -179,8 +179,10 @@ void RiscVJit::CompIR_FAssign(IRInst inst) { switch (inst.op) { case IROp::FMov: - fpr.MapDirtyIn(inst.dest, inst.src1); - FMV(32, fpr.R(inst.dest), fpr.R(inst.src1)); + if (inst.dest != inst.src1) { + fpr.MapDirtyIn(inst.dest, inst.src1); + FMV(32, fpr.R(inst.dest), fpr.R(inst.src1)); + } break; case IROp::FAbs: From 6819acd29f564ee594ac3c37377353f43b5a3e27 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 12:45:07 -0700 Subject: [PATCH 152/167] irjit: Fix flag on float cond move. --- Core/MIPS/IR/IRInst.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 738b2151f0..0b32b2bf07 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -122,7 +122,7 @@ static const IRMeta irMeta[] = { { IROp::SetCtrlVFPU, "SetCtrlVFPU", "TC" }, { IROp::SetCtrlVFPUReg, "SetCtrlVFPUReg", "TG" }, { IROp::SetCtrlVFPUFReg, "SetCtrlVFPUFReg", "TF" }, - { IROp::FCmovVfpuCC, "FCmovVfpuCC", "FFI" }, + { IROp::FCmovVfpuCC, "FCmovVfpuCC", "FFI", IRFLAG_SRC3DST }, { IROp::FCmpVfpuBit, "FCmpVfpuBit", "IFF" }, { IROp::FCmpVfpuAggregate, "FCmpVfpuAggregate", "I" }, { IROp::Vec4Init, "Vec4Init", "Vv" }, From f3240393fa1626c15efc4eddaee9ee0c885ae37c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 12:45:25 -0700 Subject: [PATCH 153/167] irjit: Use vf for vfpu regs, v0 is a gpr. --- Core/MIPS/IR/IRInst.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 0b32b2bf07..828de2ae21 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -269,21 +269,21 @@ void DisassembleParam(char *buf, int bufSize, u8 param, char type, u32 constant) break; case 'F': if (param >= 32) { - snprintf(buf, bufSize, "v%d", param - 32); + snprintf(buf, bufSize, "vf%d", param - 32); } else { snprintf(buf, bufSize, "f%d", param); } break; case 'V': if (param >= 32) { - snprintf(buf, bufSize, "v%d..v%d", param - 32, param - 32 + 3); + snprintf(buf, bufSize, "vf%d..vf%d", param - 32, param - 32 + 3); } else { snprintf(buf, bufSize, "f%d..f%d", param, param + 3); } break; case '2': if (param >= 32) { - snprintf(buf, bufSize, "v%d,v%d", param - 32, param - 32 + 1); + snprintf(buf, bufSize, "vf%d,vf%d", param - 32, param - 32 + 1); } else { snprintf(buf, bufSize, "f%d,f%d", param, param + 1); } From 09f3842a32b6bc2a34e521bc1d0f53b327de777e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 13:39:53 -0700 Subject: [PATCH 154/167] riscv: Fix VFPU compare typos. --- Core/MIPS/RiscV/RiscVCompFPU.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index ead8244712..5e0fb66156 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -423,7 +423,7 @@ void RiscVJit::CompIR_FCompare(IRInst inst) { case VC_NZ: fpr.MapReg(inst.src1); // Zero is either 0x10 or 0x08. - FCLASS(32, SCRATCH1, gpr.R(inst.src1)); + FCLASS(32, SCRATCH1, fpr.R(inst.src1)); ANDI(SCRATCH1, SCRATCH1, 0x18); if ((inst.dest & 4) == 0) SNEZ(SCRATCH1, SCRATCH1); @@ -434,7 +434,7 @@ void RiscVJit::CompIR_FCompare(IRInst inst) { case VC_NN: fpr.MapReg(inst.src1); // NAN is either 0x100 or 0x200. - FCLASS(32, SCRATCH1, gpr.R(inst.src1)); + FCLASS(32, SCRATCH1, fpr.R(inst.src1)); ANDI(SCRATCH1, SCRATCH1, 0x300); if ((inst.dest & 4) == 0) SNEZ(SCRATCH1, SCRATCH1); @@ -445,7 +445,7 @@ void RiscVJit::CompIR_FCompare(IRInst inst) { case VC_NI: fpr.MapReg(inst.src1); // Infinity is either 0x80 or 0x01. - FCLASS(32, SCRATCH1, gpr.R(inst.src1)); + FCLASS(32, SCRATCH1, fpr.R(inst.src1)); ANDI(SCRATCH1, SCRATCH1, 0x81); if ((inst.dest & 4) == 0) SNEZ(SCRATCH1, SCRATCH1); @@ -456,7 +456,7 @@ void RiscVJit::CompIR_FCompare(IRInst inst) { case VC_NS: fpr.MapReg(inst.src1); // Infinity is either 0x80 or 0x01, NAN is either 0x100 or 0x200. - FCLASS(32, SCRATCH1, gpr.R(inst.src1)); + FCLASS(32, SCRATCH1, fpr.R(inst.src1)); ANDI(SCRATCH1, SCRATCH1, 0x381); if ((inst.dest & 4) == 0) SNEZ(SCRATCH1, SCRATCH1); From d1dc346899728f27fe0e23b5d3b533503250094f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 14:05:43 -0700 Subject: [PATCH 155/167] riscv: Fix pointer add/sub. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index 1b29413797..3b4fe4ec79 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -39,7 +39,7 @@ void RiscVJit::CompIR_Arith(IRInst inst) { CONDITIONAL_DISABLE; bool allowPtrMath = true; -#ifndef MASKED_PSP_MEMORY +#ifdef MASKED_PSP_MEMORY // Since we modify it, we can't safely. allowPtrMath = false; #endif From e34736fbb2a2df8444b09d860afaaab2ce0fe734 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 09:12:09 -0700 Subject: [PATCH 156/167] riscv: Reduce norms in Slt/Sltu overlap cases. We can skip an SEXT.W in common cases where the dest and src overlap. --- Core/MIPS/RiscV/RiscVCompALU.cpp | 35 ++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompALU.cpp b/Core/MIPS/RiscV/RiscVCompALU.cpp index 3b4fe4ec79..5cb2ff256c 100644 --- a/Core/MIPS/RiscV/RiscVCompALU.cpp +++ b/Core/MIPS/RiscV/RiscVCompALU.cpp @@ -374,21 +374,27 @@ void RiscVJit::CompIR_Compare(IRInst inst) { RiscVReg rhs = INVALID_REG; switch (inst.op) { case IROp::Slt: - // Not using the NORM32 flag so we don't confuse ourselves on overlap. - gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + gpr.SpillLock(inst.dest, inst.src1, inst.src2); + gpr.MapReg(inst.src1); + gpr.MapReg(inst.src2); NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true); + gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); + gpr.ReleaseSpillLock(inst.dest, inst.src1, inst.src2); + SLT(gpr.R(inst.dest), lhs, rhs); - gpr.MarkDirty(gpr.R(inst.dest), true); break; case IROp::SltConst: - // Not using the NORM32 flag so we don't confuse ourselves on overlap. - gpr.MapDirtyIn(inst.dest, inst.src1); if (inst.constant == 0) { // Basically, getting the sign bit. Let's shift instead. + gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32); SRLIW(gpr.R(inst.dest), gpr.R(inst.src1), 31); } else { + gpr.SpillLock(inst.dest, inst.src1); + gpr.MapReg(inst.src1); NormalizeSrc1(inst, &lhs, SCRATCH1, false); + gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); + gpr.ReleaseSpillLock(inst.dest, inst.src1); if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) { SLTI(gpr.R(inst.dest), lhs, (int32_t)inst.constant); @@ -396,26 +402,31 @@ void RiscVJit::CompIR_Compare(IRInst inst) { LI(SCRATCH2, (int32_t)inst.constant); SLT(gpr.R(inst.dest), lhs, SCRATCH2); } + gpr.MarkDirty(gpr.R(inst.dest), true); } - gpr.MarkDirty(gpr.R(inst.dest), true); break; case IROp::SltU: - // Not using the NORM32 flag so we don't confuse ourselves on overlap. - gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2); + gpr.SpillLock(inst.dest, inst.src1, inst.src2); + gpr.MapReg(inst.src1); + gpr.MapReg(inst.src2); // It's still fine to sign extend, the biggest just get even bigger. NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true); + gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); + gpr.ReleaseSpillLock(inst.dest, inst.src1, inst.src2); + SLTU(gpr.R(inst.dest), lhs, rhs); - gpr.MarkDirty(gpr.R(inst.dest), true); break; case IROp::SltUConst: - // Not using the NORM32 flag so we don't confuse ourselves on overlap. - gpr.MapDirtyIn(inst.dest, inst.src1); if (inst.constant == 0) { gpr.SetImm(inst.dest, 0); } else { + gpr.SpillLock(inst.dest, inst.src1); + gpr.MapReg(inst.src1); NormalizeSrc1(inst, &lhs, SCRATCH1, false); + gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32); + gpr.ReleaseSpillLock(inst.dest, inst.src1); // We sign extend because we're comparing against something normalized. // It's also the most efficient to set. @@ -425,8 +436,6 @@ void RiscVJit::CompIR_Compare(IRInst inst) { LI(SCRATCH2, (int32_t)inst.constant); SLTU(gpr.R(inst.dest), lhs, SCRATCH2); } - - gpr.MarkDirty(gpr.R(inst.dest), true); } break; From 9f917488c34b0c7773ba59ca9eae3d49cd2a2d11 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 09:11:16 -0700 Subject: [PATCH 157/167] riscv: Fix PC in disassembly. --- Core/MIPS/JitCommon/JitCommon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/MIPS/JitCommon/JitCommon.cpp b/Core/MIPS/JitCommon/JitCommon.cpp index 4156732d5d..b6b3df1205 100644 --- a/Core/MIPS/JitCommon/JitCommon.cpp +++ b/Core/MIPS/JitCommon/JitCommon.cpp @@ -340,7 +340,7 @@ std::vector DisassembleRV64(const u8 *data, int size) { } invalid_flush(); - riscv_disasm_inst(temp, sizeof(temp), rv64, i * 4, inst); + riscv_disasm_inst(temp, sizeof(temp), rv64, (uintptr_t)data + i, inst); lines.push_back(ReplaceAll(temp, "\t", " ")); i += (int)len; From 5ef4b2b5fafad6eb91ad5a80af02b637d94b3a84 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 13:27:25 -0700 Subject: [PATCH 158/167] riscv: Fix assert when flushing not mapped reg. --- Core/MIPS/RiscV/RiscVRegCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index 25f7b5db59..43905d1f83 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -663,7 +663,6 @@ void RiscVRegCache::MapDirtyDirtyInIn(IRRegIndex rd1, IRRegIndex rd2, IRRegIndex void RiscVRegCache::FlushRiscVReg(RiscVReg r) { _dbg_assert_(r > X0 && r <= X31); _dbg_assert_(ar[r].mipsReg != MIPS_REG_ZERO); - _dbg_assert_(!mr[ar[r].mipsReg].isStatic); if (r == INVALID_REG) { ERROR_LOG(JIT, "FlushRiscVReg called on invalid register %d", r); return; @@ -673,6 +672,7 @@ void RiscVRegCache::FlushRiscVReg(RiscVReg r) { _dbg_assert_(!ar[r].isDirty); return; } + _dbg_assert_(!mr[ar[r].mipsReg].isStatic); if (mr[ar[r].mipsReg].isStatic) { ERROR_LOG(JIT, "Cannot FlushRiscVReg a statically mapped register"); return; From 45d44c1d4f6854190ee280baae5f2326d63d9734 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 09:18:42 -0700 Subject: [PATCH 159/167] riscv: Implement block debug interface. This gives us the target disasm in jit compare, bloat, etc. --- Core/MIPS/RiscV/RiscVJit.cpp | 79 +++++++++++++++++++++++++++++++++++- Core/MIPS/RiscV/RiscVJit.h | 20 ++++++++- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index bf3bf4e60a..6470fd1c91 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -69,7 +69,8 @@ static void LogDebugNotCompiled() { WARN_LOG(JIT, "Most not compiled op: %s (%d)", worstName, worstVal); } -RiscVJit::RiscVJit(MIPSState *mipsState) : IRJit(mipsState), gpr(mipsState, &jo), fpr(mipsState, &jo) { +RiscVJit::RiscVJit(MIPSState *mipsState) + : IRJit(mipsState), gpr(mipsState, &jo), fpr(mipsState, &jo), debugInterface_(blocks_, *this) { // Automatically disable incompatible options. if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) { jo.enablePointerify = false; @@ -98,6 +99,10 @@ void RiscVJit::RunLoopUntil(u64 globalticks) { ((void (*)())enterDispatcher_)(); } +JitBlockCacheDebugInterface *MIPSComp::RiscVJit::GetBlockCacheDebugInterface() { + return &debugInterface_; +} + static void NoBlockExits() { _assert_msg_(false, "Never exited block, invalid IR?"); } @@ -616,4 +621,76 @@ RiscVReg RiscVJit::NormalizeR(IRRegIndex rs, IRRegIndex rd, RiscVReg tempReg) { } } +RiscVBlockCacheDebugInterface::RiscVBlockCacheDebugInterface(IRBlockCache &irBlocks, RiscVJit &jit) + : irBlocks_(irBlocks), jit_(jit) {} + +int RiscVBlockCacheDebugInterface::GetNumBlocks() const { + return irBlocks_.GetNumBlocks(); +} + +int RiscVBlockCacheDebugInterface::GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly) const { + return irBlocks_.GetBlockNumberFromStartAddress(em_address, realBlocksOnly); +} + +void RiscVBlockCacheDebugInterface::GetBlockCodeRange(int blockNum, int *startOffset, int *size) const { + int blockOffset = irBlocks_.GetBlock(blockNum)->GetTargetOffset(); + int endOffset; + // We assume linear allocation. Maybe a bit dangerous, should always be right. + if (blockNum + 1 >= GetNumBlocks()) { + // Last block, get from current code pointer. + endOffset = (int)jit_.GetOffset(jit_.GetCodePointer()); + } else { + endOffset = irBlocks_.GetBlock(blockNum + 1)->GetTargetOffset(); + _assert_msg_(endOffset >= blockOffset, "Next block not sequential, block=%d/%08x, next=%d/%08x", blockNum, blockOffset, blockNum + 1, endOffset); + } + + *startOffset = blockOffset; + *size = endOffset - blockOffset; +} + +JitBlockDebugInfo RiscVBlockCacheDebugInterface::GetBlockDebugInfo(int blockNum) const { + JitBlockDebugInfo debugInfo = irBlocks_.GetBlockDebugInfo(blockNum); + + int blockOffset, codeSize; + GetBlockCodeRange(blockNum, &blockOffset, &codeSize); + debugInfo.targetDisasm = DisassembleRV64(jit_.GetBasePtr() + blockOffset, codeSize); + return debugInfo; +} + +void RiscVBlockCacheDebugInterface::ComputeStats(BlockCacheStats &bcStats) const { + double totalBloat = 0.0; + double maxBloat = 0.0; + double minBloat = 1000000000.0; + int numBlocks = GetNumBlocks(); + for (int i = 0; i < numBlocks; ++i) { + const IRBlock &b = *irBlocks_.GetBlock(i); + + // RISC-V (jit) size. + int blockOffset, codeSize; + GetBlockCodeRange(i, &blockOffset, &codeSize); + if (codeSize == 0) + continue; + + // MIPS (PSP) size. + u32 origAddr, mipsBytes; + b.GetRange(origAddr, mipsBytes); + + double bloat = (double)codeSize / (double)mipsBytes; + if (bloat < minBloat) { + minBloat = bloat; + bcStats.minBloatBlock = origAddr; + } + if (bloat > maxBloat) { + maxBloat = bloat; + bcStats.maxBloatBlock = origAddr; + } + totalBloat += bloat; + bcStats.bloatMap[bloat] = origAddr; + } + bcStats.numBlocks = numBlocks; + bcStats.minBloat = minBloat; + bcStats.maxBloat = maxBloat; + bcStats.avgBloat = totalBloat / (double)numBlocks; +} + } // namespace MIPSComp diff --git a/Core/MIPS/RiscV/RiscVJit.h b/Core/MIPS/RiscV/RiscVJit.h index da7295d1af..74c221fecb 100644 --- a/Core/MIPS/RiscV/RiscVJit.h +++ b/Core/MIPS/RiscV/RiscVJit.h @@ -28,6 +28,23 @@ namespace MIPSComp { +class RiscVJit; + +class RiscVBlockCacheDebugInterface : public JitBlockCacheDebugInterface { +public: + RiscVBlockCacheDebugInterface(IRBlockCache &irBlocks, RiscVJit &jit); + int GetNumBlocks() const; + int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true) const; + JitBlockDebugInfo GetBlockDebugInfo(int blockNum) const; + void ComputeStats(BlockCacheStats &bcStats) const; + +private: + void GetBlockCodeRange(int blockNum, int *startOffset, int *size) const; + + IRBlockCache &irBlocks_; + RiscVJit &jit_; +}; + class RiscVJit : public RiscVGen::RiscVCodeBlock, public IRJit { public: RiscVJit(MIPSState *mipsState); @@ -43,7 +60,7 @@ public: void ClearCache() override; - // TODO: GetBlockCacheDebugInterface, block linking? + JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override; protected: bool CompileTargetBlock(IRBlock *block, int block_num, bool preload) override; @@ -116,6 +133,7 @@ private: RiscVRegCache gpr; RiscVRegCacheFPU fpr; + RiscVBlockCacheDebugInterface debugInterface_; const u8 *enterDispatcher_ = nullptr; From 020706f545146012a79dd87a9069c8b0873824ad Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 09:21:03 -0700 Subject: [PATCH 160/167] riscv: Implement float saturate clamping. --- Core/MIPS/RiscV/RiscVCompFPU.cpp | 67 ++++++++++++++++++++++------ Core/MIPS/RiscV/RiscVRegCacheFPU.cpp | 20 ++++++--- Core/MIPS/RiscV/RiscVRegCacheFPU.h | 1 + 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index ead8244712..12f36c9b40 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -275,10 +275,55 @@ void RiscVJit::CompIR_FCvt(IRInst inst) { void RiscVJit::CompIR_FSat(IRInst inst) { CONDITIONAL_DISABLE; + RiscVReg tempReg = INVALID_REG; + FixupBranch skipLower; + FixupBranch finishLower; + FixupBranch skipHigher; switch (inst.op) { case IROp::FSat0_1: + tempReg = fpr.MapDirtyInTemp(inst.dest, inst.src1); + if (inst.dest != inst.src1) + FMV(32, fpr.R(inst.dest), fpr.R(inst.src1)); + + // First, set SCRATCH1 = clamp to zero, SCRATCH2 = clamp to one. + FCVT(FConv::S, FConv::W, tempReg, R_ZERO); + // FLE here is intentional to convert -0.0 to +0.0. + FLE(32, SCRATCH1, fpr.R(inst.src1), tempReg); + LI(SCRATCH2, 1.0f); + FMV(FMv::W, FMv::X, tempReg, SCRATCH2); + FLT(32, SCRATCH2, tempReg, fpr.R(inst.src1)); + + skipLower = BEQ(SCRATCH1, R_ZERO); + FCVT(FConv::S, FConv::W, fpr.R(inst.dest), R_ZERO); + finishLower = J(); + + SetJumpTarget(skipLower); + skipHigher = BEQ(SCRATCH2, R_ZERO); + // Still has 1.0 in it. + FMV(32, fpr.R(inst.dest), tempReg); + + SetJumpTarget(finishLower); + SetJumpTarget(skipHigher); + break; + case IROp::FSatMinus1_1: - CompIR_Generic(inst); + tempReg = fpr.MapDirtyInTemp(inst.dest, inst.src1); + if (inst.dest != inst.src1) + FMV(32, fpr.R(inst.dest), fpr.R(inst.src1)); + + // First, set SCRATCH1 = clamp to negative, SCRATCH2 = clamp to positive. + LI(SCRATCH2, -1.0f); + FMV(FMv::W, FMv::X, tempReg, SCRATCH2); + FLT(32, SCRATCH1, fpr.R(inst.src1), tempReg); + FNEG(32, tempReg, tempReg); + FLT(32, SCRATCH2, tempReg, fpr.R(inst.src1)); + + // But we can actually do one branch, using sign-injection to keep the original sign. + OR(SCRATCH1, SCRATCH1, SCRATCH2); + + skipLower = BEQ(SCRATCH1, R_ZERO); + FSGNJ(32, fpr.R(inst.dest), tempReg, fpr.R(inst.dest)); + SetJumpTarget(skipLower); break; default: @@ -549,6 +594,7 @@ void RiscVJit::CompIR_FSpecial(IRInst inst) { } }; + RiscVReg tempReg = INVALID_REG; switch (inst.op) { case IROp::FSin: callFuncF_F(&vfpu_sin); @@ -559,30 +605,25 @@ void RiscVJit::CompIR_FSpecial(IRInst inst) { break; case IROp::FRSqrt: - fpr.MapDirtyIn(inst.dest, inst.src1); + tempReg = fpr.MapDirtyInTemp(inst.dest, inst.src1); FSQRT(32, fpr.R(inst.dest), fpr.R(inst.src1)); // Ugh, we can't really avoid a temp here. Probably not worth a permanent one. LI(SCRATCH1, 1.0f); - { - // TODO: Smarter allocation of a temp reg? - RiscVReg tempReg = fpr.R(inst.dest) == F31 ? F30 : F31; - fpr.FlushRiscVReg(tempReg); - FMV(FMv::W, FMv::X, tempReg, SCRATCH1); - FDIV(32, fpr.R(inst.dest), tempReg, fpr.R(inst.dest)); - } + FMV(FMv::W, FMv::X, tempReg, SCRATCH1); + FDIV(32, fpr.R(inst.dest), tempReg, fpr.R(inst.dest)); break; case IROp::FRecip: - fpr.MapDirtyIn(inst.dest, inst.src1); - LI(SCRATCH1, 1.0f); if (inst.dest != inst.src1) { // This is the easy case. + fpr.MapDirtyIn(inst.dest, inst.src1); + LI(SCRATCH1, 1.0f); FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1); FDIV(32, fpr.R(inst.dest), fpr.R(inst.dest), fpr.R(inst.src1)); } else { - RiscVReg tempReg = fpr.R(inst.dest) == F31 ? F30 : F31; - fpr.FlushRiscVReg(tempReg); + tempReg = fpr.MapDirtyInTemp(inst.dest, inst.src1); + LI(SCRATCH1, 1.0f); FMV(FMv::W, FMv::X, tempReg, SCRATCH1); FDIV(32, fpr.R(inst.dest), tempReg, fpr.R(inst.src1)); } diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp index 7721477731..48d0400d1d 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp @@ -184,8 +184,7 @@ void RiscVRegCacheFPU::MapInIn(IRRegIndex rd, IRRegIndex rs) { SpillLock(rd, rs); MapReg(rd); MapReg(rs); - ReleaseSpillLock(rd); - ReleaseSpillLock(rs); + ReleaseSpillLock(rd, rs); } void RiscVRegCacheFPU::MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad) { @@ -193,8 +192,7 @@ void RiscVRegCacheFPU::MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad) bool load = !avoidLoad || rd == rs; MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT); MapReg(rs); - ReleaseSpillLock(rd); - ReleaseSpillLock(rs); + ReleaseSpillLock(rd, rs); } void RiscVRegCacheFPU::MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad) { @@ -203,9 +201,17 @@ void RiscVRegCacheFPU::MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT); MapReg(rt); MapReg(rs); - ReleaseSpillLock(rd); - ReleaseSpillLock(rs); - ReleaseSpillLock(rt); + ReleaseSpillLock(rd, rs, rt); +} + +RiscVReg RiscVRegCacheFPU::MapDirtyInTemp(IRRegIndex rd, IRRegIndex rs, bool avoidLoad) { + SpillLock(rd, rs); + bool load = !avoidLoad || rd == rs; + MapReg(rd, load ? MIPSMap::DIRTY : MIPSMap::NOINIT); + MapReg(rs); + RiscVReg temp = AllocateReg(); + ReleaseSpillLock(rd, rs); + return temp; } void RiscVRegCacheFPU::Map4DirtyIn(IRRegIndex rdbase, IRRegIndex rsbase, bool avoidLoad) { diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.h b/Core/MIPS/RiscV/RiscVRegCacheFPU.h index 57d93fdb83..30f897d43d 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.h +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.h @@ -64,6 +64,7 @@ public: void MapInIn(IRRegIndex rd, IRRegIndex rs); void MapDirtyIn(IRRegIndex rd, IRRegIndex rs, bool avoidLoad = true); void MapDirtyInIn(IRRegIndex rd, IRRegIndex rs, IRRegIndex rt, bool avoidLoad = true); + RiscVGen::RiscVReg MapDirtyInTemp(IRRegIndex rd, IRRegIndex rs, bool avoidLoad = true); void Map4DirtyIn(IRRegIndex rdbase, IRRegIndex rsbase, bool avoidLoad = true); void Map4DirtyInIn(IRRegIndex rdbase, IRRegIndex rsbase, IRRegIndex rtbase, bool avoidLoad = true); void FlushBeforeCall(); From f87027101143d5c8dc53dafa9774509474ed2347 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 13:29:13 -0700 Subject: [PATCH 161/167] riscv: Spill registers more intelligently. --- CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 ++ Core/MIPS/IR/IRAnalysis.cpp | 135 +++++++++++++++++++++++++++ Core/MIPS/IR/IRAnalysis.h | 44 +++++++++ Core/MIPS/IR/IRInst.cpp | 1 - Core/MIPS/IR/IRJit.cpp | 1 - Core/MIPS/IR/IRPassSimplify.cpp | 31 +----- Core/MIPS/MIPSAnalyst.cpp | 2 +- Core/MIPS/RiscV/RiscVJit.cpp | 13 +-- Core/MIPS/RiscV/RiscVRegCache.cpp | 36 +++++-- Core/MIPS/RiscV/RiscVRegCache.h | 9 +- Core/MIPS/RiscV/RiscVRegCacheFPU.cpp | 33 +++++-- Core/MIPS/RiscV/RiscVRegCacheFPU.h | 8 +- UWP/CoreUWP/CoreUWP.vcxproj | 2 + UWP/CoreUWP/CoreUWP.vcxproj.filters | 6 ++ android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 18 files changed, 271 insertions(+), 62 deletions(-) create mode 100644 Core/MIPS/IR/IRAnalysis.cpp create mode 100644 Core/MIPS/IR/IRAnalysis.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ca3c64c92..1a91796708 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1539,6 +1539,8 @@ set(CoreExtra) set(CoreExtraLibs) set(CoreExtra ${CoreExtra} + Core/MIPS/IR/IRAnalysis.cpp + Core/MIPS/IR/IRAnalysis.h Core/MIPS/IR/IRCompALU.cpp Core/MIPS/IR/IRCompBranch.cpp Core/MIPS/IR/IRCompFPU.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index b2ae24174f..9794f02f81 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -580,6 +580,7 @@ + @@ -1164,6 +1165,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index a4f56f5eb1..e437d0d813 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1237,6 +1237,9 @@ MIPS\RiscV + + MIPS\IR + @@ -1992,6 +1995,9 @@ MIPS\RiscV + + MIPS\IR + diff --git a/Core/MIPS/IR/IRAnalysis.cpp b/Core/MIPS/IR/IRAnalysis.cpp new file mode 100644 index 0000000000..0a8b861b89 --- /dev/null +++ b/Core/MIPS/IR/IRAnalysis.cpp @@ -0,0 +1,135 @@ +// Copyright (c) 2016- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Core/MIPS/IR/IRAnalysis.h" + +static bool IRReadsFrom(const IRInst &inst, int reg, char type, bool directly = false) { + const IRMeta *m = GetIRMeta(inst.op); + + if (m->types[1] == type && inst.src1 == reg) { + return true; + } + if (m->types[2] == type && inst.src2 == reg) { + return true; + } + if ((m->flags & (IRFLAG_SRC3 | IRFLAG_SRC3DST)) != 0 && m->types[0] == type && inst.src3 == reg) { + return true; + } + if (!directly) { + if (inst.op == IROp::Interpret || inst.op == IROp::CallReplacement || inst.op == IROp::Syscall || inst.op == IROp::Break) + return true; + if (inst.op == IROp::Breakpoint || inst.op == IROp::MemoryCheck) + return true; + } + return false; +} + +bool IRReadsFromFPR(const IRInst &inst, int reg, bool directly) { + if (IRReadsFrom(inst, reg, 'F', directly)) + return true; + + const IRMeta *m = GetIRMeta(inst.op); + + // We also need to check V and 2. Indirect reads already checked, don't check again. + if (m->types[1] == 'V' && reg >= inst.src1 && reg < inst.src1 + 4) + return true; + if (m->types[1] == '2' && reg >= inst.src1 && reg < inst.src1 + 2) + return true; + if (m->types[2] == 'V' && reg >= inst.src2 && reg < inst.src2 + 4) + return true; + if (m->types[2] == '2' && reg >= inst.src2 && reg < inst.src2 + 2) + return true; + if ((m->flags & (IRFLAG_SRC3 | IRFLAG_SRC3DST)) != 0) { + if (m->types[0] == 'V' && reg >= inst.src3 && reg <= inst.src3 + 4) + return true; + if (m->types[0] == '2' && reg >= inst.src3 && reg <= inst.src3 + 2) + return true; + } + return false; +} + +bool IRReadsFromGPR(const IRInst &inst, int reg, bool directly) { + return IRReadsFrom(inst, reg, 'G', directly); +} + +int IRDestGPR(const IRInst &inst) { + const IRMeta *m = GetIRMeta(inst.op); + + if ((m->flags & IRFLAG_SRC3) == 0 && m->types[0] == 'G') { + return inst.dest; + } + return -1; +} + +bool IRWritesToGPR(const IRInst &inst, int reg) { + return IRDestGPR(inst) == reg; +} + +bool IRWritesToFPR(const IRInst &inst, int reg) { + const IRMeta *m = GetIRMeta(inst.op); + + // Doesn't write to anything. + if ((m->flags & IRFLAG_SRC3) != 0) + return false; + + if (m->types[0] == 'F' && reg == inst.dest) + return true; + if (m->types[0] == 'V' && reg >= inst.dest && reg < inst.dest + 4) + return true; + if (m->types[0] == '2' && reg >= inst.dest && reg < inst.dest + 2) + return true; + return false; +} + +IRUsage IRNextGPRUsage(int gpr, const IRSituation &info) { + // Exclude any "special" regs from this logic for now. + if (gpr >= 32) + return IRUsage::UNKNOWN; + + int count = std::min(info.numInstructions - info.currentIndex, info.lookaheadCount); + for (int i = 0; i < count; ++i) { + const IRInst inst = info.instructions[info.currentIndex + i]; + if (IRReadsFromGPR(inst, gpr)) + return IRUsage::READ; + // We say WRITE when the current instruction writes. It's not useful for spilling. + if (IRDestGPR(inst) == gpr) + return i == 0 ? IRUsage::WRITE : IRUsage::CLOBBERED; + } + + return IRUsage::UNUSED; +} + +IRUsage IRNextFPRUsage(int fpr, const IRSituation &info) { + // Let's only pay attention to standard FP regs and temps. + // See MIPS.h for these offsets. + if (fpr < 0 || (fpr >= 160 && fpr < 192) || fpr >= 208) + return IRUsage::UNKNOWN; + + int count = std::min(info.numInstructions - info.currentIndex, info.lookaheadCount); + for (int i = 0; i < count; ++i) { + const IRInst inst = info.instructions[info.currentIndex + i]; + + if (IRReadsFromFPR(inst, fpr)) + return IRUsage::READ; + // We say WRITE when the current instruction writes. It's not useful for spilling. + if (IRWritesToFPR(inst, fpr)) { + return i == 0 ? IRUsage::WRITE : IRUsage::CLOBBERED; + } + } + + return IRUsage::UNUSED; +} diff --git a/Core/MIPS/IR/IRAnalysis.h b/Core/MIPS/IR/IRAnalysis.h new file mode 100644 index 0000000000..2b5ff598b4 --- /dev/null +++ b/Core/MIPS/IR/IRAnalysis.h @@ -0,0 +1,44 @@ +// Copyright (c) 2016- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Core/MIPS/IR/IRInst.h" + +bool IRReadsFromFPR(const IRInst &inst, int reg, bool directly = false); +bool IRReadsFromGPR(const IRInst &inst, int reg, bool directly = false); +bool IRWritesToGPR(const IRInst &inst, int reg); +bool IRWritesToFPR(const IRInst &inst, int reg); +int IRDestGPR(const IRInst &inst); + +struct IRSituation { + int lookaheadCount; + int currentIndex; + const IRInst *instructions; + int numInstructions; +}; + +enum class IRUsage { + UNKNOWN, + UNUSED, + READ, + WRITE, + CLOBBERED, +}; + +IRUsage IRNextGPRUsage(int gpr, const IRSituation &info); +IRUsage IRNextFPRUsage(int fpr, const IRSituation &info); diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 738b2151f0..ff623776a3 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -1,6 +1,5 @@ #include "Common/CommonFuncs.h" #include "Core/MIPS/IR/IRInst.h" -#include "Core/MIPS/IR/IRPassSimplify.h" #include "Core/MIPS/MIPSDebugInterface.h" // Legend diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 1253ca5061..039b8ee197 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -36,7 +36,6 @@ #include "Core/MIPS/MIPSTables.h" #include "Core/MIPS/IR/IRRegCache.h" #include "Core/MIPS/IR/IRJit.h" -#include "Core/MIPS/IR/IRPassSimplify.h" #include "Core/MIPS/IR/IRInterpreter.h" #include "Core/MIPS/JitCommon/JitCommon.h" #include "Core/Reporting.h" diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 11b8e5292c..9e0f12c587 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -6,6 +6,7 @@ #include "Common/Data/Convert/SmallDataConvert.h" #include "Common/Log.h" #include "Core/Config.h" +#include "Core/MIPS/IR/IRAnalysis.h" #include "Core/MIPS/IR/IRInterpreter.h" #include "Core/MIPS/IR/IRPassSimplify.h" #include "Core/MIPS/IR/IRRegCache.h" @@ -803,27 +804,6 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts return logBlocks; } -bool IRReadsFromGPR(const IRInst &inst, int reg, bool directly = false) { - const IRMeta *m = GetIRMeta(inst.op); - - if (m->types[1] == 'G' && inst.src1 == reg) { - return true; - } - if (m->types[2] == 'G' && inst.src2 == reg) { - return true; - } - if ((m->flags & (IRFLAG_SRC3 | IRFLAG_SRC3DST)) != 0 && m->types[0] == 'G' && inst.src3 == reg) { - return true; - } - if (!directly) { - if (inst.op == IROp::Interpret || inst.op == IROp::CallReplacement || inst.op == IROp::Syscall || inst.op == IROp::Break) - return true; - if (inst.op == IROp::Breakpoint || inst.op == IROp::MemoryCheck) - return true; - } - return false; -} - IRInst IRReplaceSrcGPR(const IRInst &inst, int fromReg, int toReg) { IRInst newInst = inst; const IRMeta *m = GetIRMeta(inst.op); @@ -840,15 +820,6 @@ IRInst IRReplaceSrcGPR(const IRInst &inst, int fromReg, int toReg) { return newInst; } -int IRDestGPR(const IRInst &inst) { - const IRMeta *m = GetIRMeta(inst.op); - - if ((m->flags & IRFLAG_SRC3) == 0 && m->types[0] == 'G') { - return inst.dest; - } - return -1; -} - IRInst IRReplaceDestGPR(const IRInst &inst, int fromReg, int toReg) { IRInst newInst = inst; const IRMeta *m = GetIRMeta(inst.op); diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 4cfe9a2292..1cdeeb00ba 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -813,7 +813,7 @@ namespace MIPSAnalyst { break; } - if (reg > 32) { + if (reg >= 32) { return USAGE_UNKNOWN; } diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index bf3bf4e60a..a9724d1045 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -111,19 +111,20 @@ bool RiscVJit::CompileTargetBlock(IRBlock *block, int block_num, bool preload) { // TODO: Block linking, checked entries and such. - gpr.Start(); - fpr.Start(); + gpr.Start(block); + fpr.Start(block); for (int i = 0; i < block->GetNumInstructions(); ++i) { const IRInst &inst = block->GetInstructions()[i]; + gpr.SetIRIndex(i); + fpr.SetIRIndex(i); + CompileIRInst(inst); - if (jo.Disabled(JitDisable::REGALLOC_GPR)) { + if (jo.Disabled(JitDisable::REGALLOC_GPR)) gpr.FlushAll(); - } - if (jo.Disabled(JitDisable::REGALLOC_FPR)) { + if (jo.Disabled(JitDisable::REGALLOC_FPR)) fpr.FlushAll(); - } // Safety check, in case we get a bunch of really large jit ops without a lot of branching. if (GetSpaceLeft() < 0x800) { diff --git a/Core/MIPS/RiscV/RiscVRegCache.cpp b/Core/MIPS/RiscV/RiscVRegCache.cpp index 25f7b5db59..8885905f85 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.cpp +++ b/Core/MIPS/RiscV/RiscVRegCache.cpp @@ -21,6 +21,7 @@ #include "Common/CPUDetect.h" #include "Core/MIPS/IR/IRInst.h" +#include "Core/MIPS/IR/IRAnalysis.h" #include "Core/MIPS/RiscV/RiscVRegCache.h" #include "Core/MIPS/JitCommon/JitState.h" #include "Core/Reporting.h" @@ -36,7 +37,7 @@ void RiscVRegCache::Init(RiscVEmitter *emitter) { emit_ = emitter; } -void RiscVRegCache::Start() { +void RiscVRegCache::Start(MIPSComp::IRBlock *irBlock) { if (!initialReady_) { SetupInitialRegs(); initialReady_ = true; @@ -56,6 +57,9 @@ void RiscVRegCache::Start() { mr[statics[i].mr].isStatic = true; mr[statics[i].mr].spillLock = true; } + + irBlock_ = irBlock; + irIndex_ = 0; } void RiscVRegCache::SetupInitialRegs() { @@ -362,8 +366,6 @@ allocate: } // Still nothing. Let's spill a reg and goto 10. - // TODO: Use age or something to choose which register to spill? - // TODO: Spill dirty regs first? or opposite? bool clobbered; RiscVReg bestToSpill = FindBestToSpill(true, &clobbered); if (bestToSpill == INVALID_REG) { @@ -392,6 +394,12 @@ RiscVReg RiscVRegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) { static const int UNUSED_LOOKAHEAD_OPS = 30; + IRSituation info; + info.lookaheadCount = UNUSED_LOOKAHEAD_OPS; + info.currentIndex = irIndex_; + info.instructions = irBlock_->GetInstructions(); + info.numInstructions = irBlock_->GetNumInstructions(); + *clobbered = false; for (int i = 0; i < allocCount; i++) { RiscVReg reg = allocOrder[i]; @@ -401,16 +409,24 @@ RiscVReg RiscVRegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) { continue; // As it's in alloc-order, we know it's not static so we don't need to check for that. + IRUsage usage = IRNextGPRUsage(ar[reg].mipsReg, info); - // TODO: Look for clobbering in the IRInst array with index? - - // Not awesome. A used reg. Let's try to avoid spilling. - // TODO: Actually check if we'd be spilling. - if (unusedOnly) { - continue; + // Awesome, a clobbered reg. Let's use it. + if (usage == IRUsage::CLOBBERED) { + // TODO: Check HI/LO clobber together if we combine. + bool canClobber = true; + if (canClobber) { + *clobbered = true; + return reg; + } } - return reg; + // Not awesome. A used reg. Let's try to avoid spilling. + if (!unusedOnly || usage == IRUsage::UNUSED) { + // TODO: Use age or something to choose which register to spill? + // TODO: Spill dirty regs first? or opposite? + return reg; + } } return INVALID_REG; diff --git a/Core/MIPS/RiscV/RiscVRegCache.h b/Core/MIPS/RiscV/RiscVRegCache.h index 10305f5e25..ffe373c639 100644 --- a/Core/MIPS/RiscV/RiscVRegCache.h +++ b/Core/MIPS/RiscV/RiscVRegCache.h @@ -19,6 +19,7 @@ #include "Common/RiscVEmitter.h" #include "Core/MIPS/MIPS.h" +#include "Core/MIPS/IR/IRJit.h" namespace RiscVJitConstants { @@ -101,8 +102,10 @@ public: ~RiscVRegCache() {} void Init(RiscVGen::RiscVEmitter *emitter); - // TODO: Maybe pass in IR block and start PC for logging/debugging? - void Start(); + void Start(MIPSComp::IRBlock *irBlock); + void SetIRIndex(int index) { + irIndex_ = index; + } // Protect the arm register containing a MIPS register from spilling, to ensure that // it's being kept allocated. @@ -176,6 +179,8 @@ private: MIPSState *mips_; RiscVGen::RiscVEmitter *emit_ = nullptr; MIPSComp::JitOptions *jo_; + MIPSComp::IRBlock *irBlock_ = nullptr; + int irIndex_ = 0; enum { NUM_RVREG = 32, // 31 actual registers, plus the zero/sp register which is not mappable. diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp index 48d0400d1d..c99261b9f9 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.cpp @@ -20,6 +20,8 @@ #endif #include "Common/CPUDetect.h" +#include "Core/MIPS/IR/IRInst.h" +#include "Core/MIPS/IR/IRAnalysis.h" #include "Core/MIPS/RiscV/RiscVRegCacheFPU.h" #include "Core/MIPS/JitCommon/JitState.h" #include "Core/Reporting.h" @@ -34,7 +36,7 @@ void RiscVRegCacheFPU::Init(RiscVEmitter *emitter) { emit_ = emitter; } -void RiscVRegCacheFPU::Start() { +void RiscVRegCacheFPU::Start(MIPSComp::IRBlock *irBlock) { if (!initialReady_) { SetupInitialRegs(); initialReady_ = true; @@ -43,6 +45,9 @@ void RiscVRegCacheFPU::Start() { memcpy(ar, arInitial_, sizeof(ar)); memcpy(mr, mrInitial_, sizeof(mr)); pendingFlush_ = false; + + irBlock_ = irBlock; + irIndex_ = 0; } void RiscVRegCacheFPU::SetupInitialRegs() { @@ -130,8 +135,6 @@ allocate: } // Still nothing. Let's spill a reg and goto 10. - // TODO: Use age or something to choose which register to spill? - // TODO: Spill dirty regs first? or opposite? bool clobbered; RiscVReg bestToSpill = FindBestToSpill(true, &clobbered); if (bestToSpill == INVALID_REG) { @@ -160,21 +163,33 @@ RiscVReg RiscVRegCacheFPU::FindBestToSpill(bool unusedOnly, bool *clobbered) { static const int UNUSED_LOOKAHEAD_OPS = 30; + IRSituation info; + info.lookaheadCount = UNUSED_LOOKAHEAD_OPS; + info.currentIndex = irIndex_; + info.instructions = irBlock_->GetInstructions(); + info.numInstructions = irBlock_->GetNumInstructions(); + *clobbered = false; for (int i = 0; i < allocCount; i++) { RiscVReg reg = allocOrder[i]; if (ar[reg - F0].mipsReg != IRREG_INVALID && mr[ar[reg - F0].mipsReg].spillLock) continue; - // TODO: Look for clobbering in the IRInst array with index? + // As it's in alloc-order, we know it's not static so we don't need to check for that. + IRUsage usage = IRNextFPRUsage(ar[reg - F0].mipsReg, info); - // Not awesome. A used reg. Let's try to avoid spilling. - // TODO: Actually check if we'd be spilling. - if (unusedOnly) { - continue; + // Awesome, a clobbered reg. Let's use it. + if (usage == IRUsage::CLOBBERED) { + *clobbered = true; + return reg; } - return reg; + // Not awesome. A used reg. Let's try to avoid spilling. + if (!unusedOnly || usage == IRUsage::UNUSED) { + // TODO: Use age or something to choose which register to spill? + // TODO: Spill dirty regs first? or opposite? + return reg; + } } return INVALID_REG; diff --git a/Core/MIPS/RiscV/RiscVRegCacheFPU.h b/Core/MIPS/RiscV/RiscVRegCacheFPU.h index 30f897d43d..9edda169aa 100644 --- a/Core/MIPS/RiscV/RiscVRegCacheFPU.h +++ b/Core/MIPS/RiscV/RiscVRegCacheFPU.h @@ -46,8 +46,10 @@ public: ~RiscVRegCacheFPU() {} void Init(RiscVGen::RiscVEmitter *emitter); - // TODO: Maybe pass in IR block and start PC for logging/debugging? - void Start(); + void Start(MIPSComp::IRBlock *irBlock); + void SetIRIndex(int index) { + irIndex_ = index; + } // Protect the RISC-V register containing a MIPS register from spilling, to ensure that // it's being kept allocated. @@ -89,6 +91,8 @@ private: MIPSState *mips_; RiscVGen::RiscVEmitter *emit_ = nullptr; MIPSComp::JitOptions *jo_; + MIPSComp::IRBlock *irBlock_ = nullptr; + int irIndex_ = 0; enum { // On RiscV, each of the 32 registers are full 128-bit. No sharing of components! diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index ca53e7d496..5af1ee85d0 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -302,6 +302,7 @@ + @@ -561,6 +562,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index f6ccb537f0..363504bba7 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -645,6 +645,9 @@ MIPS\IR + + MIPS\IR + MIPS\IR @@ -1667,6 +1670,9 @@ MIPS\IR + + MIPS\IR + MIPS\IR diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 4fe1d0df45..494a4d6316 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -390,6 +390,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/MIPS/MIPSVFPUFallbacks.cpp.arm \ $(SRC)/Core/MIPS/MIPSCodeUtils.cpp.arm \ $(SRC)/Core/MIPS/MIPSDebugInterface.cpp \ + $(SRC)/Core/MIPS/IR/IRAnalysis.cpp \ $(SRC)/Core/MIPS/IR/IRFrontend.cpp \ $(SRC)/Core/MIPS/IR/IRJit.cpp \ $(SRC)/Core/MIPS/IR/IRCompALU.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 8c6a90ca06..70c00dfee2 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -671,6 +671,7 @@ SOURCES_CXX += \ $(COREDIR)/MIPS/JitCommon/JitCommon.cpp \ $(COREDIR)/MIPS/JitCommon/JitState.cpp \ $(COREDIR)/MIPS/JitCommon/JitBlockCache.cpp \ + $(COREDIR)/MIPS/IR/IRAnalysis.cpp \ $(COREDIR)/MIPS/IR/IRCompALU.cpp \ $(COREDIR)/MIPS/IR/IRCompBranch.cpp \ $(COREDIR)/MIPS/IR/IRCompFPU.cpp \ From c24dca12bbb5eb16abf23da832c98d8bd7fbdff4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 16:06:55 -0700 Subject: [PATCH 162/167] Build: Fix link issue for rv64 disasm. --- Core/MIPS/RiscV/RiscVAsm.cpp | 2 ++ Core/MIPS/RiscV/RiscVJit.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Core/MIPS/RiscV/RiscVAsm.cpp b/Core/MIPS/RiscV/RiscVAsm.cpp index 87acfa8290..e07b59760b 100644 --- a/Core/MIPS/RiscV/RiscVAsm.cpp +++ b/Core/MIPS/RiscV/RiscVAsm.cpp @@ -232,10 +232,12 @@ void RiscVJit::GenerateFixedCode(const JitOptions &jo) { // Leave this at the end, add more stuff above. if (enableDisasm) { +#if PPSSPP_ARCH(RISCV64) std::vector lines = DisassembleRV64(start, GetCodePtr() - start); for (auto s : lines) { INFO_LOG(JIT, "%s", s.c_str()); } +#endif } // Let's spare the pre-generated code from unprotect-reprotect. diff --git a/Core/MIPS/RiscV/RiscVJit.cpp b/Core/MIPS/RiscV/RiscVJit.cpp index 9d0d5ecb92..04e0c23bf3 100644 --- a/Core/MIPS/RiscV/RiscVJit.cpp +++ b/Core/MIPS/RiscV/RiscVJit.cpp @@ -652,9 +652,11 @@ void RiscVBlockCacheDebugInterface::GetBlockCodeRange(int blockNum, int *startOf JitBlockDebugInfo RiscVBlockCacheDebugInterface::GetBlockDebugInfo(int blockNum) const { JitBlockDebugInfo debugInfo = irBlocks_.GetBlockDebugInfo(blockNum); +#if PPSSPP_ARCH(RISCV64) int blockOffset, codeSize; GetBlockCodeRange(blockNum, &blockOffset, &codeSize); debugInfo.targetDisasm = DisassembleRV64(jit_.GetBasePtr() + blockOffset, codeSize); +#endif return debugInfo; } From 0d0029fc9d7580df48b66e51fa5b7be219420520 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 30 Jul 2023 17:45:36 -0700 Subject: [PATCH 163/167] riscv: Add bitmanip ops to disasm. --- Core/MIPS/JitCommon/JitCommon.cpp | 2 +- ext/riscv-disas.cpp | 115 +++++++++++++++++++++++++++++- ext/riscv-disas.h | 43 +++++++++++ 3 files changed, 158 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/JitCommon/JitCommon.cpp b/Core/MIPS/JitCommon/JitCommon.cpp index b6b3df1205..1adfda298a 100644 --- a/Core/MIPS/JitCommon/JitCommon.cpp +++ b/Core/MIPS/JitCommon/JitCommon.cpp @@ -335,7 +335,7 @@ std::vector DisassembleRV64(const u8 *data, int size) { // Force align in case we're somehow unaligned. len = 2 - ((uintptr_t)data & 1); invalid_count += (int)len; - i +=(int) len; + i += (int)len; continue; } diff --git a/ext/riscv-disas.cpp b/ext/riscv-disas.cpp index 282bfa77bb..7eba6b51eb 100644 --- a/ext/riscv-disas.cpp +++ b/ext/riscv-disas.cpp @@ -674,6 +674,49 @@ const rv_opcode_data opcode_data[] = { { "fsflags", rv_codec_i_csr, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, { "fsrmi", rv_codec_i_csr, rv_fmt_rd_zimm, NULL, 0, 0, 0 }, { "fsflagsi", rv_codec_i_csr, rv_fmt_rd_zimm, NULL, 0, 0, 0 }, + { "add.uw", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "andn", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "bclr", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "bclri", rv_codec_i_sh7, rv_fmt_rd_rs1_imm, NULL, 0, 0, 0 }, + { "bext", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "bexti", rv_codec_i_sh7, rv_fmt_rd_rs1_imm, NULL, 0, 0, 0 }, + { "binv", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "binvi", rv_codec_i_sh7, rv_fmt_rd_rs1_imm, NULL, 0, 0, 0 }, + { "bset", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "bseti", rv_codec_i_sh7, rv_fmt_rd_rs1_imm, NULL, 0, 0, 0 }, + { "clmul", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "clmulh", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "clmulr", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "clz", rv_codec_i, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "clzw", rv_codec_i, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "cpop", rv_codec_i, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "cpopw", rv_codec_i, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "ctz", rv_codec_i, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "ctzw", rv_codec_i, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "max", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "maxu", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "min", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "minu", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "orc.b", rv_codec_i, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "orn", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "rev8", rv_codec_i, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "rol", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "rolw", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "ror", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "rori", rv_codec_i_sh7, rv_fmt_rd_rs1_imm, NULL, 0, 0, 0 }, + { "roriw", rv_codec_i_sh7, rv_fmt_rd_rs1_imm, NULL, 0, 0, 0 }, + { "rorw", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "sext.b", rv_codec_i, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "sext.h", rv_codec_i, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, + { "sh1add", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "sh1add.uw", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "sh2add", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "sh2add.uw", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "sh3add", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "sh3add.uw", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "slli.uw", rv_codec_i_sh6, rv_fmt_rd_rs1_imm, NULL, 0, 0, 0 }, + { "xnor", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 }, + { "zext.h", rv_codec_i, rv_fmt_rd_rs1, NULL, 0, 0, 0 }, }; /* CSR names */ @@ -1091,6 +1134,18 @@ static void decode_inst_opcode(rv_decode *dec, rv_isa isa) case 1: switch (((inst >> 27) & 0b11111)) { case 0: op = rv_op_slli; break; + case 5: op = rv_op_bseti; break; + case 9: op = rv_op_bclri; break; + case 12: + switch (((inst >> 20) & 0b11111)) { + case 0: op = rv_op_clz; break; + case 1: op = rv_op_ctz; break; + case 2: op = rv_op_cpop; break; + case 4: op = rv_op_sext_b; break; + case 5: op = rv_op_sext_h; break; + } + break; + case 13: op = rv_op_binvi; break; } break; case 2: op = rv_op_slti; break; @@ -1099,7 +1154,20 @@ static void decode_inst_opcode(rv_decode *dec, rv_isa isa) case 5: switch (((inst >> 27) & 0b11111)) { case 0: op = rv_op_srli; break; + case 5: + switch (((inst >> 20) & 0b1111111)) { + case 7: op = rv_op_orc_b; break; + } + break; case 8: op = rv_op_srai; break; + case 9: op = rv_op_bexti; break; + case 12: op = rv_op_rori; break; + case 13: + switch (((inst >> 20) & 0b1111111)) { + case 24: if (isa == rv32) op = rv_op_rev8; break; + case 56: if (isa == rv64) op = rv_op_rev8; break; + } + break; } break; case 6: op = rv_op_ori; break; @@ -1113,12 +1181,22 @@ static void decode_inst_opcode(rv_decode *dec, rv_isa isa) case 1: switch (((inst >> 25) & 0b1111111)) { case 0: op = rv_op_slliw; break; + case 4: op = rv_op_slli_uw; break; + case 5: op = rv_op_slli_uw; break; + case 48: + switch (((inst >> 20) & 0b11111)) { + case 0: op = rv_op_clzw; break; + case 1: op = rv_op_ctzw; break; + case 2: op = rv_op_cpopw; break; + } + break; } break; case 5: switch (((inst >> 25) & 0b1111111)) { case 0: op = rv_op_srliw; break; case 32: op = rv_op_sraiw; break; + case 48: op = rv_op_roriw; break; } break; } @@ -1207,8 +1285,32 @@ static void decode_inst_opcode(rv_decode *dec, rv_isa isa) case 13: op = rv_op_divu; break; case 14: op = rv_op_rem; break; case 15: op = rv_op_remu; break; + case 36: + switch (((inst >> 20) & 0b11111)) { + case 0: if (isa == rv32) op = rv_op_zext_h; break; + } + break; + case 41: op = rv_op_clmul; break; + case 42: op = rv_op_clmulr; break; + case 43: op = rv_op_clmulh; break; + case 44: op = rv_op_min; break; + case 45: op = rv_op_minu; break; + case 46: op = rv_op_max; break; + case 47: op = rv_op_maxu; break; + case 130: op = rv_op_sh1add; break; + case 132: op = rv_op_sh2add; break; + case 134: op = rv_op_sh3add; break; + case 161: op = rv_op_bset; break; case 256: op = rv_op_sub; break; + case 260: op = rv_op_xnor; break; case 261: op = rv_op_sra; break; + case 262: op = rv_op_orn; break; + case 263: op = rv_op_andn; break; + case 289: op = rv_op_bclr; break; + case 293: op = rv_op_bext; break; + case 385: op = rv_op_rol; break; + case 389: op = rv_op_ror; break; + case 417: op = rv_op_binv; break; } break; case 13: op = rv_op_lui; break; @@ -1222,8 +1324,19 @@ static void decode_inst_opcode(rv_decode *dec, rv_isa isa) case 13: op = rv_op_divuw; break; case 14: op = rv_op_remw; break; case 15: op = rv_op_remuw; break; + case 32: op = rv_op_add_uw; break; + case 36: + switch (((inst >> 20) & 0b11111)) { + case 0: if (isa == rv64) op = rv_op_zext_h; break; + } + break; + case 130: op = rv_op_sh1add_uw; break; + case 132: op = rv_op_sh2add_uw; break; + case 134: op = rv_op_sh3add_uw; break; case 256: op = rv_op_subw; break; case 261: op = rv_op_sraw; break; + case 385: op = rv_op_rolw; break; + case 389: op = rv_op_rorw; break; } break; case 16: @@ -2195,7 +2308,7 @@ static void decode_inst_lift_pseudo(rv_decode *dec) static char *append(char *buf, const char *src, const char *end) { while (buf < end && *src) - *buf++ = *src++; + *buf++ = *src++; return buf; } diff --git a/ext/riscv-disas.h b/ext/riscv-disas.h index ebf5571743..b7fcdfa509 100644 --- a/ext/riscv-disas.h +++ b/ext/riscv-disas.h @@ -524,6 +524,49 @@ typedef enum { rv_op_fsflags, rv_op_fsrmi, rv_op_fsflagsi, + rv_op_add_uw, + rv_op_andn, + rv_op_bclr, + rv_op_bclri, + rv_op_bext, + rv_op_bexti, + rv_op_binv, + rv_op_binvi, + rv_op_bset, + rv_op_bseti, + rv_op_clmul, + rv_op_clmulh, + rv_op_clmulr, + rv_op_clz, + rv_op_clzw, + rv_op_cpop, + rv_op_cpopw, + rv_op_ctz, + rv_op_ctzw, + rv_op_max, + rv_op_maxu, + rv_op_min, + rv_op_minu, + rv_op_orc_b, + rv_op_orn, + rv_op_rev8, + rv_op_rol, + rv_op_rolw, + rv_op_ror, + rv_op_rori, + rv_op_roriw, + rv_op_rorw, + rv_op_sext_b, + rv_op_sext_h, + rv_op_sh1add, + rv_op_sh1add_uw, + rv_op_sh2add, + rv_op_sh2add_uw, + rv_op_sh3add, + rv_op_sh3add_uw, + rv_op_slli_uw, + rv_op_xnor, + rv_op_zext_h, } rv_op; /* structures */ From 3861e97a94e7bd7fda9a2fe0ff24e834eca519d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 17 Jul 2023 01:11:37 +0200 Subject: [PATCH 164/167] Experiment with the collapsible header thingy. Slightly increase the font size of headers. --- Common/UI/View.cpp | 1 + GPU/GPUState.cpp | 2 +- UI/DevScreens.cpp | 87 +++++++++++++++++++++++----------------------- UI/Theme.cpp | 8 ++--- 4 files changed, 50 insertions(+), 48 deletions(-) diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index 82c2497a7a..587db038ab 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -607,6 +607,7 @@ CollapsibleHeader::CollapsibleHeader(bool *toggle, const std::string &text, Layo void CollapsibleHeader::Draw(UIContext &dc) { Style style = dc.theme->itemStyle; + style.background.color = 0; if (HasFocus()) style = dc.theme->itemFocusedStyle; if (down_) style = dc.theme->itemDownStyle; if (!IsEnabled()) style = dc.theme->itemDisabledStyle; diff --git a/GPU/GPUState.cpp b/GPU/GPUState.cpp index c5f9882202..94bc154c6c 100644 --- a/GPU/GPUState.cpp +++ b/GPU/GPUState.cpp @@ -382,7 +382,7 @@ static const char *const gpuUseFlagNames[32] = { "GPU_USE_VS_RANGE_CULLING", "GPU_USE_BLEND_MINMAX", "GPU_USE_LOGIC_OP", - "N/A", + "GPU_USE_FRAGMENT_UBERSHADER", "GPU_USE_TEXTURE_NPOT", "GPU_USE_ANISOTROPY", "GPU_USE_CLEAR_RAM_HACK", diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 18f887de38..a9d1bce918 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -472,42 +472,43 @@ void SystemInfoScreen::CreateTabs() { LinearLayout *deviceSpecs = AddTab("Device Info", si->T("Device Info")); - deviceSpecs->Add(new ItemHeader(si->T("System Information"))); - deviceSpecs->Add(new InfoItem(si->T("System Name", "Name"), System_GetProperty(SYSPROP_NAME))); + CollapsibleSection *systemInfo = deviceSpecs->Add(new CollapsibleSection(si->T("System Information"))); + systemInfo->Add(new InfoItem(si->T("System Name", "Name"), System_GetProperty(SYSPROP_NAME))); #if PPSSPP_PLATFORM(ANDROID) - deviceSpecs->Add(new InfoItem(si->T("System Version"), StringFromInt(System_GetPropertyInt(SYSPROP_SYSTEMVERSION)))); + systemInfo->Add(new InfoItem(si->T("System Version"), StringFromInt(System_GetPropertyInt(SYSPROP_SYSTEMVERSION)))); #endif - deviceSpecs->Add(new InfoItem(si->T("Lang/Region"), System_GetProperty(SYSPROP_LANGREGION))); + systemInfo->Add(new InfoItem(si->T("Lang/Region"), System_GetProperty(SYSPROP_LANGREGION))); std::string board = System_GetProperty(SYSPROP_BOARDNAME); if (!board.empty()) - deviceSpecs->Add(new InfoItem(si->T("Board"), board)); - deviceSpecs->Add(new InfoItem(si->T("ABI"), GetCompilerABI())); + systemInfo->Add(new InfoItem(si->T("Board"), board)); + systemInfo->Add(new InfoItem(si->T("ABI"), GetCompilerABI())); #ifdef _WIN32 if (IsDebuggerPresent()) { - deviceSpecs->Add(new InfoItem(si->T("Debugger Present"), di->T("Yes"))); + systemInfo->Add(new InfoItem(si->T("Debugger Present"), di->T("Yes"))); } #endif - deviceSpecs->Add(new ItemHeader(si->T("CPU Information"))); + CollapsibleSection *cpuInfo = deviceSpecs->Add(new CollapsibleSection(si->T("CPU Information"))); // Don't bother showing the CPU name if we don't have one. if (strcmp(cpu_info.brand_string, "Unknown") != 0) { - deviceSpecs->Add(new InfoItem(si->T("CPU Name", "Name"), cpu_info.brand_string)); + cpuInfo->Add(new InfoItem(si->T("CPU Name", "Name"), cpu_info.brand_string)); } int totalThreads = cpu_info.num_cores * cpu_info.logical_cpu_count; std::string cores = StringFromFormat(si->T("%d (%d per core, %d cores)"), totalThreads, cpu_info.logical_cpu_count, cpu_info.num_cores); - deviceSpecs->Add(new InfoItem(si->T("Threads"), cores)); + cpuInfo->Add(new InfoItem(si->T("Threads"), cores)); #if PPSSPP_PLATFORM(IOS) - deviceSpecs->Add(new InfoItem(si->T("JIT available"), System_GetPropertyBool(SYSPROP_CAN_JIT) ? di->T("Yes") : di->T("No"))); + cpuInfo->Add(new InfoItem(si->T("JIT available"), System_GetPropertyBool(SYSPROP_CAN_JIT) ? di->T("Yes") : di->T("No"))); #endif - deviceSpecs->Add(new ItemHeader(si->T("GPU Information"))); + + CollapsibleSection *gpuInfo = deviceSpecs->Add(new CollapsibleSection(si->T("GPU Information"))); DrawContext *draw = screenManager()->getDrawContext(); const std::string apiNameKey = draw->GetInfoString(InfoField::APINAME); const char *apiName = gr->T(apiNameKey); - deviceSpecs->Add(new InfoItem(si->T("3D API"), apiName)); + gpuInfo->Add(new InfoItem(si->T("3D API"), apiName)); // TODO: Not really vendor, on most APIs it's a device name (GL calls it vendor though). std::string vendorString; @@ -516,22 +517,22 @@ void SystemInfoScreen::CreateTabs() { } else { vendorString = draw->GetInfoString(InfoField::VENDORSTRING); } - deviceSpecs->Add(new InfoItem(si->T("Vendor"), vendorString)); + gpuInfo->Add(new InfoItem(si->T("Vendor"), vendorString)); std::string vendor = draw->GetInfoString(InfoField::VENDOR); if (vendor.size()) - deviceSpecs->Add(new InfoItem(si->T("Vendor (detected)"), vendor)); - deviceSpecs->Add(new InfoItem(si->T("Driver Version"), draw->GetInfoString(InfoField::DRIVER))); + gpuInfo->Add(new InfoItem(si->T("Vendor (detected)"), vendor)); + gpuInfo->Add(new InfoItem(si->T("Driver Version"), draw->GetInfoString(InfoField::DRIVER))); #ifdef _WIN32 if (GetGPUBackend() != GPUBackend::VULKAN) - deviceSpecs->Add(new InfoItem(si->T("Driver Version"), System_GetProperty(SYSPROP_GPUDRIVER_VERSION))); + gpuInfo->Add(new InfoItem(si->T("Driver Version"), System_GetProperty(SYSPROP_GPUDRIVER_VERSION))); #if !PPSSPP_PLATFORM(UWP) if (GetGPUBackend() == GPUBackend::DIRECT3D9) { - deviceSpecs->Add(new InfoItem(si->T("D3DCompiler Version"), StringFromFormat("%d", GetD3DCompilerVersion()))); + gpuInfo->Add(new InfoItem(si->T("D3DCompiler Version"), StringFromFormat("%d", GetD3DCompilerVersion()))); } #endif #endif if (GetGPUBackend() == GPUBackend::OPENGL) { - deviceSpecs->Add(new InfoItem(si->T("Core Context"), gl_extensions.IsCoreContext ? di->T("Active") : di->T("Inactive"))); + gpuInfo->Add(new InfoItem(si->T("Core Context"), gl_extensions.IsCoreContext ? di->T("Active") : di->T("Inactive"))); int highp_int_min = gl_extensions.range[1][5][0]; int highp_int_max = gl_extensions.range[1][5][1]; int highp_float_min = gl_extensions.range[1][2][0]; @@ -539,15 +540,15 @@ void SystemInfoScreen::CreateTabs() { if (highp_int_max != 0) { char temp[512]; snprintf(temp, sizeof(temp), "Highp int range: %d-%d", highp_int_min, highp_int_max); - deviceSpecs->Add(new InfoItem(si->T("High precision int range"), temp)); + gpuInfo->Add(new InfoItem(si->T("High precision int range"), temp)); } if (highp_float_max != 0) { char temp[512]; snprintf(temp, sizeof(temp), "Highp float range: %d-%d", highp_int_min, highp_int_max); - deviceSpecs->Add(new InfoItem(si->T("High precision float range"), temp)); + gpuInfo->Add(new InfoItem(si->T("High precision float range"), temp)); } } - deviceSpecs->Add(new InfoItem(si->T("Depth buffer format"), DataFormatToString(draw->GetDeviceCaps().preferredDepthBufferFormat))); + gpuInfo->Add(new InfoItem(si->T("Depth buffer format"), DataFormatToString(draw->GetDeviceCaps().preferredDepthBufferFormat))); std::string texCompressionFormats; // Simple non-detailed summary of supported tex compression formats. @@ -556,38 +557,38 @@ void SystemInfoScreen::CreateTabs() { if (draw->GetDataFormatSupport(Draw::DataFormat::BC1_RGBA_UNORM_BLOCK)) texCompressionFormats += "BC1-3 "; if (draw->GetDataFormatSupport(Draw::DataFormat::BC4_UNORM_BLOCK)) texCompressionFormats += "BC4-5 "; if (draw->GetDataFormatSupport(Draw::DataFormat::BC7_UNORM_BLOCK)) texCompressionFormats += "BC7 "; - deviceSpecs->Add(new InfoItem(si->T("Compressed texture formats"), texCompressionFormats)); + gpuInfo->Add(new InfoItem(si->T("Compressed texture formats"), texCompressionFormats)); - deviceSpecs->Add(new ItemHeader(si->T("OS Information"))); - deviceSpecs->Add(new InfoItem(si->T("Memory Page Size"), StringFromFormat(si->T("%d bytes"), GetMemoryProtectPageSize()))); - deviceSpecs->Add(new InfoItem(si->T("RW/RX exclusive"), PlatformIsWXExclusive() ? di->T("Active") : di->T("Inactive"))); + CollapsibleSection *osInformation = deviceSpecs->Add(new CollapsibleSection(si->T("OS Information"))); + osInformation->Add(new InfoItem(si->T("Memory Page Size"), StringFromFormat(si->T("%d bytes"), GetMemoryProtectPageSize()))); + osInformation->Add(new InfoItem(si->T("RW/RX exclusive"), PlatformIsWXExclusive() ? di->T("Active") : di->T("Inactive"))); #if PPSSPP_PLATFORM(ANDROID) - deviceSpecs->Add(new InfoItem(si->T("Sustained perf mode"), System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE) ? di->T("Supported") : di->T("Unsupported"))); + osInformation->Add(new InfoItem(si->T("Sustained perf mode"), System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE) ? di->T("Supported") : di->T("Unsupported"))); #endif const char *build = si->T("Release"); #ifdef _DEBUG build = si->T("Debug"); #endif - deviceSpecs->Add(new InfoItem(si->T("PPSSPP build"), build)); + osInformation->Add(new InfoItem(si->T("PPSSPP build"), build)); - deviceSpecs->Add(new ItemHeader(si->T("Audio Information"))); - deviceSpecs->Add(new InfoItem(si->T("Sample rate"), StringFromFormat(si->T("%d Hz"), System_GetPropertyInt(SYSPROP_AUDIO_SAMPLE_RATE)))); + CollapsibleSection *audioInformation = deviceSpecs->Add(new CollapsibleSection(si->T("Audio Information"))); + audioInformation->Add(new InfoItem(si->T("Sample rate"), StringFromFormat(si->T("%d Hz"), System_GetPropertyInt(SYSPROP_AUDIO_SAMPLE_RATE)))); int framesPerBuffer = System_GetPropertyInt(SYSPROP_AUDIO_FRAMES_PER_BUFFER); if (framesPerBuffer > 0) { - deviceSpecs->Add(new InfoItem(si->T("Frames per buffer"), StringFromFormat("%d", framesPerBuffer))); + audioInformation->Add(new InfoItem(si->T("Frames per buffer"), StringFromFormat("%d", framesPerBuffer))); } #if PPSSPP_PLATFORM(ANDROID) - deviceSpecs->Add(new InfoItem(si->T("Optimal sample rate"), StringFromFormat(si->T("%d Hz"), System_GetPropertyInt(SYSPROP_AUDIO_OPTIMAL_SAMPLE_RATE)))); - deviceSpecs->Add(new InfoItem(si->T("Optimal frames per buffer"), StringFromFormat("%d", System_GetPropertyInt(SYSPROP_AUDIO_OPTIMAL_FRAMES_PER_BUFFER)))); + audioInformation->Add(new InfoItem(si->T("Optimal sample rate"), StringFromFormat(si->T("%d Hz"), System_GetPropertyInt(SYSPROP_AUDIO_OPTIMAL_SAMPLE_RATE)))); + audioInformation->Add(new InfoItem(si->T("Optimal frames per buffer"), StringFromFormat("%d", System_GetPropertyInt(SYSPROP_AUDIO_OPTIMAL_FRAMES_PER_BUFFER)))); #endif - deviceSpecs->Add(new ItemHeader(si->T("Display Information"))); + CollapsibleSection *displayInfo = deviceSpecs->Add(new CollapsibleSection(si->T("Display Information"))); #if PPSSPP_PLATFORM(ANDROID) - deviceSpecs->Add(new InfoItem(si->T("Native Resolution"), StringFromFormat("%dx%d", + displayInfo->Add(new InfoItem(si->T("Native Resolution"), StringFromFormat("%dx%d", System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)))); - deviceSpecs->Add(new InfoItem(si->T("UI Resolution"), StringFromFormat("%dx%d (%s: %0.2f)", + displayInfo->Add(new InfoItem(si->T("UI Resolution"), StringFromFormat("%dx%d (%s: %0.2f)", g_display.dp_xres, g_display.dp_yres, si->T("DPI"), @@ -596,10 +597,10 @@ void SystemInfoScreen::CreateTabs() { #if !PPSSPP_PLATFORM(WINDOWS) // Don't show on Windows, since it's always treated as 60 there. - deviceSpecs->Add(new InfoItem(si->T("Refresh rate"), StringFromFormat(si->T("%0.2f Hz"), (float)System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE)))); + displayInfo->Add(new InfoItem(si->T("Refresh rate"), StringFromFormat(si->T("%0.2f Hz"), (float)System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE)))); #endif - deviceSpecs->Add(new ItemHeader(si->T("Version Information"))); + CollapsibleSection *versionInfo = deviceSpecs->Add(new CollapsibleSection(si->T("Version Information"))); std::string apiVersion; if (GetGPUBackend() == GPUBackend::OPENGL) { if (gl_extensions.IsGLES) { @@ -612,26 +613,26 @@ void SystemInfoScreen::CreateTabs() { if (apiVersion.size() > 30) apiVersion.resize(30); } - deviceSpecs->Add(new InfoItem(si->T("API Version"), apiVersion)); - deviceSpecs->Add(new InfoItem(si->T("Shading Language"), draw->GetInfoString(InfoField::SHADELANGVERSION))); + versionInfo->Add(new InfoItem(si->T("API Version"), apiVersion)); + versionInfo->Add(new InfoItem(si->T("Shading Language"), draw->GetInfoString(InfoField::SHADELANGVERSION))); #if PPSSPP_PLATFORM(ANDROID) std::string moga = System_GetProperty(SYSPROP_MOGA_VERSION); if (moga.empty()) { moga = si->T("(none detected)"); } - deviceSpecs->Add(new InfoItem("Moga", moga)); + versionInfo->Add(new InfoItem("Moga", moga)); #endif if (gstate_c.GetUseFlags()) { // We're in-game, and can determine these. // TODO: Call a static version of GPUCommon::CheckGPUFeatures() and derive them here directly. - deviceSpecs->Add(new ItemHeader(si->T("GPU Flags"))); + CollapsibleSection *gpuFlags = deviceSpecs->Add(new CollapsibleSection(si->T("GPU Flags"))); for (int i = 0; i < 32; i++) { if (gstate_c.Use((1 << i))) { - deviceSpecs->Add(new TextView(GpuUseFlagToString(i), new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true); + gpuFlags->Add(new TextView(GpuUseFlagToString(i), new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true); } } } diff --git a/UI/Theme.cpp b/UI/Theme.cpp index 9119259e7c..3e4b6193a2 100644 --- a/UI/Theme.cpp +++ b/UI/Theme.cpp @@ -191,12 +191,12 @@ void UpdateTheme(UIContext *ctx) { #if defined(USING_WIN_UI) || PPSSPP_PLATFORM(UWP) || defined(USING_QT_UI) ui_theme.uiFont = UI::FontStyle(FontID("UBUNTU24"), g_Config.sFont.c_str(), 22); - ui_theme.uiFontSmall = UI::FontStyle(FontID("UBUNTU24"), g_Config.sFont.c_str(), 15); - ui_theme.uiFontSmaller = UI::FontStyle(FontID("UBUNTU24"), g_Config.sFont.c_str(), 12); + ui_theme.uiFontSmall = UI::FontStyle(FontID("UBUNTU24"), g_Config.sFont.c_str(), 17); + ui_theme.uiFontSmaller = UI::FontStyle(FontID("UBUNTU24"), g_Config.sFont.c_str(), 13); #else ui_theme.uiFont = UI::FontStyle(FontID("UBUNTU24"), "", 20); - ui_theme.uiFontSmall = UI::FontStyle(FontID("UBUNTU24"), "", 14); - ui_theme.uiFontSmaller = UI::FontStyle(FontID("UBUNTU24"), "", 11); + ui_theme.uiFontSmall = UI::FontStyle(FontID("UBUNTU24"), "", 15); + ui_theme.uiFontSmaller = UI::FontStyle(FontID("UBUNTU24"), "", 12); #endif ui_theme.checkOn = ImageID("I_CHECKEDBOX"); From b1751f8b41344e1cfbf80f0a9cc4fa6f8de7d4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 31 Jul 2023 23:18:23 +0200 Subject: [PATCH 165/167] Move the corner position strings from Networking (for Chat) to Dialog (general) These strings will be used to configure positions for notifications. --- Tools/langtool/Cargo.lock | 4 ++-- UI/GameSettingsScreen.cpp | 4 ++-- assets/lang/ar_AE.ini | 18 +++++++++--------- assets/lang/az_AZ.ini | 18 +++++++++--------- assets/lang/bg_BG.ini | 18 +++++++++--------- assets/lang/ca_ES.ini | 18 +++++++++--------- assets/lang/cz_CZ.ini | 18 +++++++++--------- assets/lang/da_DK.ini | 18 +++++++++--------- assets/lang/de_DE.ini | 18 +++++++++--------- assets/lang/dr_ID.ini | 18 +++++++++--------- assets/lang/en_US.ini | 18 +++++++++--------- assets/lang/es_ES.ini | 18 +++++++++--------- assets/lang/es_LA.ini | 18 +++++++++--------- assets/lang/fa_IR.ini | 18 +++++++++--------- assets/lang/fi_FI.ini | 18 +++++++++--------- assets/lang/fr_FR.ini | 18 +++++++++--------- assets/lang/gl_ES.ini | 18 +++++++++--------- assets/lang/gr_EL.ini | 18 +++++++++--------- assets/lang/he_IL.ini | 18 +++++++++--------- assets/lang/he_IL_invert.ini | 18 +++++++++--------- assets/lang/hr_HR.ini | 18 +++++++++--------- assets/lang/hu_HU.ini | 18 +++++++++--------- assets/lang/id_ID.ini | 18 +++++++++--------- assets/lang/it_IT.ini | 18 +++++++++--------- assets/lang/ja_JP.ini | 18 +++++++++--------- assets/lang/jv_ID.ini | 18 +++++++++--------- assets/lang/ko_KR.ini | 18 +++++++++--------- assets/lang/lo_LA.ini | 18 +++++++++--------- assets/lang/lt-LT.ini | 18 +++++++++--------- assets/lang/ms_MY.ini | 18 +++++++++--------- assets/lang/nl_NL.ini | 18 +++++++++--------- assets/lang/no_NO.ini | 18 +++++++++--------- assets/lang/pl_PL.ini | 18 +++++++++--------- assets/lang/pt_BR.ini | 18 +++++++++--------- assets/lang/pt_PT.ini | 18 +++++++++--------- assets/lang/ro_RO.ini | 18 +++++++++--------- assets/lang/ru_RU.ini | 18 +++++++++--------- assets/lang/sv_SE.ini | 18 +++++++++--------- assets/lang/tg_PH.ini | 18 +++++++++--------- assets/lang/th_TH.ini | 18 +++++++++--------- assets/lang/tr_TR.ini | 18 +++++++++--------- assets/lang/uk_UA.ini | 18 +++++++++--------- assets/lang/vi_VN.ini | 18 +++++++++--------- assets/lang/zh_CN.ini | 18 +++++++++--------- assets/lang/zh_TW.ini | 18 +++++++++--------- 45 files changed, 391 insertions(+), 391 deletions(-) diff --git a/Tools/langtool/Cargo.lock b/Tools/langtool/Cargo.lock index 1d7ab7ee8c..04b9fb45f4 100644 --- a/Tools/langtool/Cargo.lock +++ b/Tools/langtool/Cargo.lock @@ -115,9 +115,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 1ae24d5a9d..16209e60d8 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -821,9 +821,9 @@ void GameSettingsScreen::CreateNetworkingSettings(UI::ViewGroup *networkingSetti networkingSettings->Add(new ItemHeader(n->T("Chat"))); networkingSettings->Add(new CheckBox(&g_Config.bEnableNetworkChat, n->T("Enable network chat", "Enable network chat"))); static const char *chatButtonPositions[] = { "Bottom Left", "Bottom Center", "Bottom Right", "Top Left", "Top Center", "Top Right", "Center Left", "Center Right", "None" }; - networkingSettings->Add(new PopupMultiChoice(&g_Config.iChatButtonPosition, n->T("Chat Button Position"), chatButtonPositions, 0, ARRAY_SIZE(chatButtonPositions), I18NCat::NETWORKING, screenManager()))->SetEnabledPtr(&g_Config.bEnableNetworkChat); + networkingSettings->Add(new PopupMultiChoice(&g_Config.iChatButtonPosition, n->T("Chat Button Position"), chatButtonPositions, 0, ARRAY_SIZE(chatButtonPositions), I18NCat::DIALOG, screenManager()))->SetEnabledPtr(&g_Config.bEnableNetworkChat); static const char *chatScreenPositions[] = { "Bottom Left", "Bottom Center", "Bottom Right", "Top Left", "Top Center", "Top Right" }; - networkingSettings->Add(new PopupMultiChoice(&g_Config.iChatScreenPosition, n->T("Chat Screen Position"), chatScreenPositions, 0, ARRAY_SIZE(chatScreenPositions), I18NCat::NETWORKING, screenManager()))->SetEnabledPtr(&g_Config.bEnableNetworkChat); + networkingSettings->Add(new PopupMultiChoice(&g_Config.iChatScreenPosition, n->T("Chat Screen Position"), chatScreenPositions, 0, ARRAY_SIZE(chatScreenPositions), I18NCat::DIALOG, screenManager()))->SetEnabledPtr(&g_Config.bEnableNetworkChat); #if (!defined(MOBILE_DEVICE) && !defined(USING_QT_UI)) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID) // Missing only iOS? networkingSettings->Add(new ItemHeader(n->T("QuickChat", "Quick Chat"))); diff --git a/assets/lang/ar_AE.ini b/assets/lang/ar_AE.ini index 6b9b5167b0..c82e09f2c8 100644 --- a/assets/lang/ar_AE.ini +++ b/assets/lang/ar_AE.ini @@ -323,8 +323,13 @@ VFPU = VFPU * PSP res = ‎* PSP حجم Active = Active Back = ‎رجوع +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = ‎إلغاء Center = ‎المركز +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = ‎إختر PPSSPP مجلد الحفظ الخاص بـ @@ -373,6 +378,7 @@ Move Up = Move Up Network Connection = ‎إتصال الشبكة NEW DATA = ‎بيانات جديدة No = ‎لا +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = ‎حسناً Old savedata detected = ‎بيانات حفظ قديمة قد إكتشفت @@ -401,6 +407,9 @@ Supported = Supported There is no data = ‎ليس هناك بيانات. Toggle All = ‎تبديل الكل Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -803,11 +812,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = ‎MAC تغيير عنوان الـ Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -841,7 +845,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = ‎الشبكة تُطلق -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset (0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -855,9 +858,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/az_AZ.ini b/assets/lang/az_AZ.ini index 44dbe2fd7f..37ac303f61 100644 --- a/assets/lang/az_AZ.ini +++ b/assets/lang/az_AZ.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Geri +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Cancel Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Network Connection NEW DATA = NEW DATA No = Xeyir +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Old savedata detected @@ -393,6 +399,9 @@ Supported = Supported There is no data = Veri yoxdur Toggle All = Toggle all Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Change MAC address Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/bg_BG.ini b/assets/lang/bg_BG.ini index 864f9fee27..5ad190a2ab 100644 --- a/assets/lang/bg_BG.ini +++ b/assets/lang/bg_BG.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Назад +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Отмени Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Мрежова връзка NEW DATA = Нови данни No = Не +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Old savedata detected @@ -393,6 +399,9 @@ Supported = Supported There is no data = Няма данни. Toggle All = Toggle all Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Change MAC address Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/ca_ES.ini b/assets/lang/ca_ES.ini index 53bdb084a5..f95e5bbbae 100644 --- a/assets/lang/ca_ES.ini +++ b/assets/lang/ca_ES.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * resolució PSP Active = Actiu Back = Enrere +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Cancel·lar Center = Centre +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Canvar aquesta configuració requereix reiniciar PPSSPP.\nReiniciar ara? Channel: = Canal: Choose PPSSPP save folder = Tria la carpeta de guardat de PPSSPP. @@ -365,6 +370,7 @@ Move Up = Pujar Network Connection = Connexión de xarxa NEW DATA = NOVES DADES DE PARTIDA No = No +None = None ObtainingIP = Obtenint direcció IP.\nPer favor espera... OK = Acceptar Old savedata detected = S'han detectar dades del joc antigues. @@ -393,6 +399,9 @@ Supported = Suportat There is no data = No hi han dades Toggle All = Canviar tot Toggle List = Veure llista +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = No suportat Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Quant guardis, la partida carregarà a una PSP, però no en versions antigues de PPSSPP. @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Change MAC address Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/cz_CZ.ini b/assets/lang/cz_CZ.ini index 9840fcfec2..64f72f9cdf 100644 --- a/assets/lang/cz_CZ.ini +++ b/assets/lang/cz_CZ.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP rozlišení Active = Active Back = Zpět +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Zrušit Center = Na střed +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Zvolit složku pro ukládání dat PPSSPP @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Síťové připojení NEW DATA = NOVÁ DATA No = Ne +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Zjištěna stará uložená data @@ -393,6 +399,9 @@ Supported = Supported There is no data = Žádná data k dispozici. Toggle All = Přepnout vše Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Pokud uložíte, můžete hru načíst na PSP, ale ne na starší verzi PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Změnit MAC adresu Change proAdhocServer Address = Změnit IP adresu serveru PRO ad hoc (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Síť zavedena -None = None Please change your Port Offset = Please change your port offset Port offset = Odchylka portu (0 = Kompatibilita s PSP) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/da_DK.ini b/assets/lang/da_DK.ini index 45676f32a2..33587c9968 100644 --- a/assets/lang/da_DK.ini +++ b/assets/lang/da_DK.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Tilbage +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Annuler Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Vælg hvilken folder PPSSPP gemmer i @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Netværksforbindelse NEW DATA = NY DATA No = Nej +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Gamle savedata fundet @@ -393,6 +399,9 @@ Supported = Supported There is no data = Der findes ingen data. Toggle All = Skift alle Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Når du gemmer vil det kunne hentes på en PSP men ikke på en ældre PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Ændre MAC adressen Change proAdhocServer Address = Ændre PRO ad hoc server IP adresse (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Netværk er initialiseret -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP kompatibilitet) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/de_DE.ini b/assets/lang/de_DE.ini index 0f49f1a5fa..eb17e8081f 100644 --- a/assets/lang/de_DE.ini +++ b/assets/lang/de_DE.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP Auflösung Active = Aktiv Back = Zurück +Bottom Center = Mitte unten +Bottom Left = links unten +Bottom Right = rechts unten Cancel = Abbrechen Center = Zentriert +Center Left = Mitte links +Center Right = Mitte rechts Changing this setting requires PPSSPP to restart. = Wenn Sie diese Einstellung ändern muss PPSSPP neu gestartet werden. Channel: = Kanal: Choose PPSSPP save folder = Wähle PPSSPP Speicherpfad @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Netzwerkverbindung NEW DATA = Neue Daten No = Nein +None = Nichts ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Alter Speicherstand entdeckt @@ -393,6 +399,9 @@ Supported = Unterstützt There is no data = Keine Daten vorhanden Toggle All = Alle umschalten Toggle List = Toggle list +Top Center = Mitte oben +Top Left = Links oben +Top Right = Rechts oben Unsupported = Nicht unterstützt Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Wenn Sie speichern, kann es von einer PSP gelesen werden, aber nicht von einem älteren PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server verbinden von Port fehlgeschlagen AM: Data from Unknown Port = AM: Dateien von Unbekannten Port Auto = Auto -Bottom Center = Mitte unten -Bottom Left = links unten -Bottom Right = rechts unten -Center Left = Mitte links -Center Right = Mitte rechts Change Mac Address = MAC-Adresse ändern Change proAdhocServer Address = proAdhocServer IP-Adresse ändern (localhost = mehrere Instanzen) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Ungültige IP oder Hostnamen Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Netzwerk initialisiert -None = Nichts Please change your Port Offset = Bitte ändern sie Ihren Port-Offset Port offset = Portverschiebung (0 = PSP Kompatibilität) Open PPSSPP Multiplayer Wiki Page = PPSSPP Ad-Hoc Wiki Seite @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Senden Send Discord Presence information = Sende Discord "Rich Presence" Information -Top Center = Mitte oben -Top Left = Links oben -Top Right = Rechts oben Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/dr_ID.ini b/assets/lang/dr_ID.ini index b2c3fb59f0..3bb3d12f38 100644 --- a/assets/lang/dr_ID.ini +++ b/assets/lang/dr_ID.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Poleboko' +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Ah edda pole' Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Network Connection NEW DATA = DATA baru No = Edda mane +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = Okemi mane Old savedata detected = Old savedata detected @@ -393,6 +399,9 @@ Supported = Supported There is no data = Ai edda data mane. Toggle All = Toggle all Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Change MAC address Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 6f02b2b217..58a0bad5bf 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -340,8 +340,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Back +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Cancel Center = Center +Center Left = Center left +Center Right = Center right Channel: = Channel: Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Choose PPSSPP save folder = Choose PPSSPP save folder @@ -390,6 +395,7 @@ Move Down = Move Down Network Connection = Network Connection NEW DATA = NEW DATA No = No +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Old savedata detected @@ -418,6 +424,9 @@ Supported = Supported There is no data = There is no data. Toggle All = Toggle all Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -791,11 +800,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Change MAC address ChangeMacSaveConfirm = Generate a new MAC address? ChangeMacSaveWarning = Some games verify the MAC address when loading savedata, so this may break old saves. @@ -829,7 +833,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset (0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -843,9 +846,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/es_ES.ini b/assets/lang/es_ES.ini index 480dad9547..a18d4d4825 100644 --- a/assets/lang/es_ES.ini +++ b/assets/lang/es_ES.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * resolución PSP Active = Activo Back = Atrás +Bottom Center = Inferior centrado +Bottom Left = Inferior izquierda +Bottom Right = Inferior derecha Cancel = Cancelar Center = Centrar +Center Left = Centrado izquierdo +Center Right = Centrado derecho Changing this setting requires PPSSPP to restart. = Cambiar esta opción requiere reiniciar PPSSPP.\n¿Reiniciar ahora? Channel: = Canal: Choose PPSSPP save folder = Elige la carpeta de guardado de PPSSPP. @@ -365,6 +370,7 @@ Move Up = Subir Network Connection = Conexión de red NEW DATA = NUEVOS DATOS DE PARTIDA No = No +None = Ninguno ObtainingIP = Obteniendo direcciónnn IP.\nPor favor espera... OK = Aceptar Old savedata detected = Se han detectado datos del juego antiguos. @@ -393,6 +399,9 @@ Supported = Soportado There is no data = La tarjeta de memoria está vacía. Toggle All = Cambiar todo Toggle List = Ver lista +Top Center = Superior centrado +Top Left = Superior izquierda +Top Right = Superior derecha Unsupported = No soportado Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Cuando guardes, la partida cargará en una PSP, pero no en versiones antiguas de PPSSPP. @@ -796,11 +805,6 @@ AdHoc Server = Servidor AdHoc AdhocServer Failed to Bind Port = Servidor Adhoc falló al unir puerto. AM: Data from Unknown Port = AM: Datos de puerto desconocido Auto = Automático -Bottom Center = Inferior centrado -Bottom Left = Inferior izquierda -Bottom Right = Inferior derecha -Center Left = Centrado izquierdo -Center Right = Centrado derecho Change Mac Address = Cambiar dirección MAC Change proAdhocServer Address = Cambiar dirección de proAdhocServer (localhost = varias instancias) ChangeMacSaveConfirm = Generar una nueva dirección MAC? @@ -834,7 +838,6 @@ Invalid IP or hostname = IP o nombre del host inválido Minimum Timeout = Tiempo de espera mínimo (en ms, 0 por defecto) Misc = Otros ajustes (por defecto = compatibilidad) Network Initialized = Juego en red iniciado -None = Ninguno Please change your Port Offset = Por favor cambia el offset del puerto. Port offset = Variar puerto de red (0 = compatible con PSP) Open PPSSPP Multiplayer Wiki Page = Página wiki sobre PPSSPP Ad-Hoc @@ -848,9 +851,6 @@ QuickChat = Chat rápido Randomize = Randomize Send = Enviar Send Discord Presence information = Enviar información a Discord "Rich Presence" -Top Center = Superior centrado -Top Left = Superior izquierda -Top Right = Superior derecha Unable to find UPnP device = Dispositivo UPnP no encontrado UPnP (port-forwarding) = UPnP (redirección de puertos) UPnP need to be reinitialized = La función de UPnP debe reinicializarse. diff --git a/assets/lang/es_LA.ini b/assets/lang/es_LA.ini index cbbea2cc65..641c32113a 100644 --- a/assets/lang/es_LA.ini +++ b/assets/lang/es_LA.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Activo Back = Atrás +Bottom Center = Abajo +Bottom Left = Abajo Izquierda +Bottom Right = Abajo Derecha Cancel = Cancelar Center = Centrar +Center Left = Izquierda +Center Right = Derecha Changing this setting requires PPSSPP to restart. = Cambiar esta opción requiere que PPSSPP se reinicie.\n¿Reiniciar ahora? Channel: = Canal: Choose PPSSPP save folder = Elige la carpeta de guardado de PPSSPP. @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Conexión de red NEW DATA = CREAR DATOS No = No +None = None ObtainingIP = Obteniendo dirección IP.\nEspera un momento... OK = OK Old savedata detected = Se han detectado datos del juego antiguos. @@ -393,6 +399,9 @@ Supported = Soportado There is no data = No se han encontrado datos del juego. Toggle All = Cambiar todo Toggle List = Cambiar lista +Top Center = Arriba +Top Left = Arriba Izquierda +Top Right = Arriba Derecha Unsupported = No es soportado Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Cuando guardes, la partida cargará en una PSP, pero no en versiones antiguas de PPSSPP. @@ -795,11 +804,6 @@ AdHoc Server = Servidor Adhoc AdhocServer Failed to Bind Port = Servidor Adhoc falló al unir puerto. AM: Data from Unknown Port = AM: Datos de puerto desconocido Auto = Auto -Bottom Center = Abajo -Bottom Left = Abajo Izquierda -Bottom Right = Abajo Derecha -Center Left = Izquierda -Center Right = Derecha Change Mac Address = Cambiar dirección MAC Change proAdhocServer Address = Cambiar dirección IP de\nservidor PRO Adhoc (localhost = multiples instancias) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = IP o nombre de host no válido Minimum Timeout = Tiempo de espera mínimo (en ms, 0 = por defecto) Misc = Otros ajustes (por defecto = compatibilidad con PSP) Network Initialized = Juego en red iniciado -None = None Please change your Port Offset = Por favor cambia el offset del puerto. Port offset = Variar puerto de red (0 = compatibilidad con PSP) Open PPSSPP Multiplayer Wiki Page = Página wiki sobre PPSSPP Ad-Hoc @@ -847,9 +850,6 @@ QuickChat = Chat rápido Randomize = Randomize Send = Enviar Send Discord Presence information = Enviar información al Discord "Rich Presence" -Top Center = Arriba -Top Left = Arriba Izquierda -Top Right = Arriba Derecha Unable to find UPnP device = No se encuentra el dispositivo UPnP UPnP (port-forwarding) = UPnP (redirección de puertos) UPnP need to be reinitialized = La función de UPnP debe reinicializarse. diff --git a/assets/lang/fa_IR.ini b/assets/lang/fa_IR.ini index 94a6ae9665..354f6ce5d4 100644 --- a/assets/lang/fa_IR.ini +++ b/assets/lang/fa_IR.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = ‎برگشت +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = ‎انصراف Center = وسط +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = تغییر دستور گرافیک گرافیک نیاز به PPSSPP برای راه اندازی مجدد دارد. راه اندازی مجدد حالا؟ Channel: = کانال: Choose PPSSPP save folder = انتخاب پوشه برای PPSSSPP @@ -365,6 +370,7 @@ Move Up = حرکت به بالا Network Connection = Network Connection NEW DATA = ‎دیتای جدید No = ‎خیر +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = ‎تایید Old savedata detected = Old savedata detected @@ -393,6 +399,9 @@ Supported = حمایت میشود There is no data = ‎.دیتایی وجود ندارد Toggle All = تغییر وضعیت همه Toggle List = تغییر وضعیت لیست +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = ساپورت نمیشود Username = Username When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Change MAC address Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/fi_FI.ini b/assets/lang/fi_FI.ini index 192e7d4825..01e14c2e77 100644 --- a/assets/lang/fi_FI.ini +++ b/assets/lang/fi_FI.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Takaisin +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Cancel Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Network Connection NEW DATA = NEW DATA No = Ei +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Old savedata detected @@ -393,6 +399,9 @@ Supported = Tuettu There is no data = Dataa ei ole. Toggle All = Toggle all Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Ei tuettu Username = Käyttäjänimi When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Change MAC address Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/fr_FR.ini b/assets/lang/fr_FR.ini index 3c538fda50..9059c48b8d 100644 --- a/assets/lang/fr_FR.ini +++ b/assets/lang/fr_FR.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = × définition PSP Active = Actif Back = Retour +Bottom Center = En bas au centre +Bottom Left = En bas à gauche +Bottom Right = En bas à droite Cancel = Annuler Center = Centrer +Center Left = Au centre à gauche +Center Right = Au centre à droite Changing this setting requires PPSSPP to restart. = La modification de ce paramètre nécessite le redémarrage de PPSSPP. Redémarrer maintenant ? Channel: = Canal : Choose PPSSPP save folder = Veuillez choisir le dossier de sauvegarde de PPSSPP. @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Connexion réseau NEW DATA = NOUVELLES DONNÉES No = Non +None = None ObtainingIP = Obtention de l'adresse IP\nVeuillez patienter... OK = OK Old savedata detected = Ancienne sauvegarde détectée @@ -393,6 +399,9 @@ Supported = Supporté There is no data = Aucune donnée trouvée. Toggle All = Tout cocher Toggle List = Afficher liste +Top Center = En haut au centre +Top Left = En haut à gauche +Top Right = En haut à droite Unsupported = Non supporté Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Si vous sauvegardez, le chargement sera possible sur une PSP, mais pas sur une ancienne version de PPSSPP. @@ -795,11 +804,6 @@ AdHoc Server = Serveur ad hoc AdhocServer Failed to Bind Port = Échec de liaison du port par le serveur ad hoc AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Automatique -Bottom Center = En bas au centre -Bottom Left = En bas à gauche -Bottom Right = En bas à droite -Center Left = Au centre à gauche -Center Right = Au centre à droite Change Mac Address = Modifier l'adresse MAC Change proAdhocServer Address = Modifier l'adresse IP du serveur ad hoc (localhost = plusieurs instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = IP ou nom de domaine invalide Minimum Timeout = Temps d'expiration minimum (forçage en ms, 0 = par défaut) Misc = Divers (par défaut = compatibilité PSP) Network Initialized = Réseau initialisé ! -None = None Please change your Port Offset = Veuillez changer votre décalage de port Port offset = Décalage de port (0 = compatibilité PSP) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Chat rapide Randomize = Randomize Send = Envoyer Send Discord Presence information = Envoyer l'information "Rich Presence" de Discord -Top Center = En haut au centre -Top Left = En haut à gauche -Top Right = En haut à droite Unable to find UPnP device = Appareil UPnP introuvable UPnP (port-forwarding) = UPnP (redirection de port) UPnP need to be reinitialized = UPnP doit être réinitialisé diff --git a/assets/lang/gl_ES.ini b/assets/lang/gl_ES.ini index 1139d09c8a..12c814bc61 100644 --- a/assets/lang/gl_ES.ini +++ b/assets/lang/gl_ES.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Atrás +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Cancelar Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Elixe a carpeta de gardado de PPSSPP. @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Conexión de rede NEW DATA = NOVOS DATOS No = Non +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = Aceptar Old savedata detected = Detectados datos de gardado antigos. @@ -393,6 +399,9 @@ Supported = Supported There is no data = A tarxeta de memoria está baleira. Toggle All = Cambiar todo Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Cando gardes, a partida cargará nunha PSP, pero non en versións antigas de PPSSPP. @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Cambiar dirección MAC Change proAdhocServer Address = Cambiar IP de proAdhocServer (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Rede inicializada -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/gr_EL.ini b/assets/lang/gr_EL.ini index 46a13c6888..f4558e3660 100644 --- a/assets/lang/gr_EL.ini +++ b/assets/lang/gr_EL.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * ανάλυση PSP Active = ΕΝΕΡΓΟ Back = ΠΙΣΩ +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = ΑΚΥΡΟ Center = ΚΕΝΤΡΟ +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Επιλογή φακέλου αποθήκευσης PPSSPP @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = ΣΥΝΔΕΣΗ ΔΙΚΤΥΟΥ NEW DATA = ΝΕΑ ΔΕΔΟΜΕΝΑ No = ΟΧΙ +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Εντοπίστηκαν παλαιά αρχεία αποθήκευσης @@ -393,6 +399,9 @@ Supported = Supported There is no data = ΔΕΝ ΥΠΑΡΧΟΥΝ ΔΕΔΟΜΕΝΑ Toggle All = ΕΝΑΛΛΑΓΗ ΟΛΩΝ Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Όταν αποθηκεύετε, θα φορτώσει στο PSP, αλλά όχι σε μια παλαιότερη έκδοση του PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Αλλαγή φυσικής διεύθυνσης (MAC) Change proAdhocServer Address = Αλλαγή διεύθυνσης IP proAdhocServer (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Μη έγκυρη IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Δίκτυο Εκκινήθηκε -None = None Please change your Port Offset = Please change your port offset Port offset = Offset πήλης(0 = για συμβατότητα PSP) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Αποστολή "Rich Presence" πληροφοριών σε Discord -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/he_IL.ini b/assets/lang/he_IL.ini index 8a3ff39392..0ee8fd9926 100644 --- a/assets/lang/he_IL.ini +++ b/assets/lang/he_IL.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = << +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = לטב Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Network Connection NEW DATA = םישדח םינותנ No = אל +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = רדסב Old savedata detected = Old savedata detected @@ -393,6 +399,9 @@ Supported = Supported There is no data = .םינותנ ןיא Toggle All = Toggle all Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Change MAC address Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/he_IL_invert.ini b/assets/lang/he_IL_invert.ini index d298194e13..f4a7b671d5 100644 --- a/assets/lang/he_IL_invert.ini +++ b/assets/lang/he_IL_invert.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = << +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = לטב Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Network Connection NEW DATA = םישדח םינותנ No = אל +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = רדסב Old savedata detected = Old savedata detected @@ -393,6 +399,9 @@ Supported = Supported There is no data = .םינותנ ןיא Toggle All = Toggle all Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Change MAC address Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/hr_HR.ini b/assets/lang/hr_HR.ini index 6c3a1fba37..84a68e2dbe 100644 --- a/assets/lang/hr_HR.ini +++ b/assets/lang/hr_HR.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Aktivno Back = Nazad +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Odustani Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Promjena ove postavke zahtijeva ponovno pokretanje PPSSPP-a. Channel: = Channel: Choose PPSSPP save folder = Izaberi PPSSPP save mapu @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Internetska veza NEW DATA = NOVA DATA No = Ne +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Uočena stara savedata @@ -393,6 +399,9 @@ Supported = Podržano There is no data = Nema date. Toggle All = Uključi sve Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Nepodržano Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Kada spremiš,učitat će se na PSP, ali ne na starijem PPSSPP-u @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Promijeni MAC addresu Change proAdhocServer Address = Promijeni PRO ad hoc server IP addresu (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Nevažeći IP ili domaćin Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Veza Uspostavljena -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset (0 = PSP kompatibilnost) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Šalji Discord "Rich Presence" informaciju -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/hu_HU.ini b/assets/lang/hu_HU.ini index f7803a3893..dbc560b78d 100644 --- a/assets/lang/hu_HU.ini +++ b/assets/lang/hu_HU.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP felbontás Active = Aktív Back = Vissza +Bottom Center = Alul középen +Bottom Left = Alul bal old. +Bottom Right = Alul jobb old. Cancel = Mégse Center = Középre +Center Left = Középen bal old. +Center Right = Középen jobb old. Changing this setting requires PPSSPP to restart. = Grafikai parancs-bufferelés megváltoztatásához újra kell indítani az alkalmazást. Újraindítod most? Channel: = Channel: Choose PPSSPP save folder = PPSSPP mentési mappa kiválasztása @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Hálózati kapcsolat NEW DATA = ÚJ MENTÉS No = Nem +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Régi mentés észlelve @@ -393,6 +399,9 @@ Supported = Támogatott There is no data = Nincs adat. Toggle All = Összes átkapcsolása Toggle List = Toggle list +Top Center = Felül középen +Top Left = Felül bal old. +Top Right = Felül jobb old. Unsupported = Nem támogatott Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Ha mentesz, PSP-n vissza tudod majd tölteni, de egy régebbi PPSSPP-n nem @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Alul középen -Bottom Left = Alul bal old. -Bottom Right = Alul jobb old. -Center Left = Középen bal old. -Center Right = Középen jobb old. Change Mac Address = MAC cím megváltoztatása Change proAdhocServer Address = ProAdhoc szerver címe (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Érvénytelen IP vagy kiszolgáló név Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Hálózat inicializálva -None = None Please change your Port Offset = Please change your port offset Port offset = Port eltolás/offset (0 = PSP kompatibilitás) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Küldés Send Discord Presence information = Discord "Rich Presence" információ küldése -Top Center = Felül középen -Top Left = Felül bal old. -Top Right = Felül jobb old. Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/id_ID.ini b/assets/lang/id_ID.ini index cef54215aa..ce05491d38 100644 --- a/assets/lang/id_ID.ini +++ b/assets/lang/id_ID.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Aktif Back = Kembali +Bottom Center = Tengah bawah +Bottom Left = Kiri bawah +Bottom Right = Kanan bawah Cancel = Batal Center = Tengah +Center Left = Kiri tengah +Center Right = Kanan tengah Changing this setting requires PPSSPP to restart. = Mengubah pengaturan ini memerlukan memulai ulang PPSSPP. Channel: = Saluran: Choose PPSSPP save folder = Pilih berkas simpanan PPSSPP @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Koneksi Jaringan NEW DATA = DATA BARU No = Tidak +None = Tidak ada ObtainingIP = Mendapatkan alamat IP.\nHarap tunggu... OK = Oke Old savedata detected = Simpanan data lama terdeteksi @@ -393,6 +399,9 @@ Supported = Didukung There is no data = Tidak ada data. Toggle All = Alihkan semua Toggle List = Daftar pengalihan +Top Center = Tengah atas +Top Left = Kiri atas +Top Right = Kanan atas Unsupported = Tidak didukung Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Setelah anda menyimpan, ini akan dapat dimuat di PSP, namun tidak di PPSSPP lawas @@ -795,11 +804,6 @@ AdHoc Server = Server Ad Hoc AdhocServer Failed to Bind Port = Server Ad Hoc gagal memuat port AM: Data from Unknown Port = AM: Data dari port tidak dikenal Auto = Otomatis -Bottom Center = Tengah bawah -Bottom Left = Kiri bawah -Bottom Right = Kanan bawah -Center Left = Kiri tengah -Center Right = Kanan tengah Change Mac Address = Ganti alamat MAC Change proAdhocServer Address = Ganti alamat IP pelayan PRO ad hoc (lokal) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = IP atau nama host tidak valid Minimum Timeout = Batas waktu minimum (batas dalam ms, 0 = bawaan) Misc = Lain-lain (bawaan= kompatibilitas PSP) Network Initialized = Jaringan terinisiasi -None = Tidak ada Please change your Port Offset = Harap ubah letak port Anda Port offset = Letak port (0 = kecocokan dengan PSP) Open PPSSPP Multiplayer Wiki Page = Halaman Wiki PPSSPP Ad-Hoc @@ -847,9 +850,6 @@ QuickChat = Obrolan cepat Randomize = Randomize Send = Kirim Send Discord Presence information = Kirim informasi "Kritik dan Saran" ke Discord -Top Center = Tengah atas -Top Left = Kiri atas -Top Right = Kanan atas Unable to find UPnP device = Tidak dapat menemukan perangkat UPnP UPnP (port-forwarding) = UPnP (penerusan port) UPnP need to be reinitialized = UPnP perlu diinisialisasi ulang diff --git a/assets/lang/it_IT.ini b/assets/lang/it_IT.ini index 089d7b7425..f84d30c366 100644 --- a/assets/lang/it_IT.ini +++ b/assets/lang/it_IT.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * definizione PSP Active = Attiva Back = Indietro +Bottom Center = In basso al centro +Bottom Left = In basso a sinistra +Bottom Right = In basso a destra Cancel = Annulla Center = Centra +Center Left = Al centro a sinistra +Center Right = Al centro a destra Changing this setting requires PPSSPP to restart. = La modifica di questa impostazione richiede il riavvio di PPSSPP. Channel: = Canale: Choose PPSSPP save folder = Scegli la cartella di salvataggio PPSSPP @@ -365,6 +370,7 @@ Move Up = Sposta su Network Connection = Connessione di Rete NEW DATA = NUOVI DATI No = No +None = None ObtainingIP = Cerco di ottenere l'indirizzo IP.\nAttendere, prego... OK = OK Old savedata detected = Rilevati vecchi dati salvati @@ -393,6 +399,9 @@ Supported = Supportato There is no data = Non ci sono dati. Toggle All = Attiva Tutto Toggle List = Mostra lista +Top Center = In alto al centro +Top Left = In alto a sinistra +Top Right = In alto a destra Unsupported = Non supportato Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Salvando, sarà possibile effettuare il caricamento su una PSP, ma non su un vecchio PPSSPP. @@ -767,11 +776,6 @@ AdHoc Server = Server Ad hoc AdhocServer Failed to Bind Port = Il server Ad hoc non è riuscito a collegarsi alla porta AM: Data from Unknown Port = AM: Dati da porta sconosciuta Auto = Auto -Bottom Center = In basso al centro -Bottom Left = In basso a sinistra -Bottom Right = In basso a destra -Center Left = Al centro a sinistra -Center Right = Al centro a destra Change Mac Address = Cambia indirizzo MAC Change proAdhocServer Address = Cambia indirizzo server IP PRO ad hoc (localhost = istanze multiple) ChangeMacSaveConfirm = Generare un nuovo indirizzo MAC? @@ -805,7 +809,6 @@ Invalid IP or hostname = Indirizzo IP o nome del dominio non valido Minimum Timeout = Timeout minimo (forza in ms, 0 = predefinito) Misc = Varie (predefinito = compatibilità PSP) Network Initialized = Rete inizializzata -None = None Please change your Port Offset = Prego, cambiare l'offset della porta Port offset = Offset Porta (0 = compatibilità PSP) Open PPSSPP Multiplayer Wiki Page = Pagina Wiki Ad-Hoc PPSSPP @@ -819,9 +822,6 @@ QuickChat = Chat rapida Randomize = Randomize Send = Invia Send Discord Presence information = Invia informazioni Discord "Rich Presence" -Top Center = In alto al centro -Top Left = In alto a sinistra -Top Right = In alto a destra Unable to find UPnP device = Impossibile trovare il dispositivo UPnP UPnP (port-forwarding) = UPnP (reindirizzamento della porta) UPnP need to be reinitialized = UPnP dev'essere reinizializzato diff --git a/assets/lang/ja_JP.ini b/assets/lang/ja_JP.ini index 49bb1d9770..04fca0a49a 100644 --- a/assets/lang/ja_JP.ini +++ b/assets/lang/ja_JP.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP 解像度 Active = アクティブ Back = 戻る +Bottom Center = 下中央 +Bottom Left = 左下 +Bottom Right = 右下 Cancel = キャンセル Center = センタリング +Center Left = 左中央 +Center Right = 右中央 Changing this setting requires PPSSPP to restart. = この設定を変更するには、PPSSPPを再起動する必要があります。 Channel: = Channel: Choose PPSSPP save folder = PPSSPPのセーブ用フォルダを選択する @@ -365,6 +370,7 @@ Move Up = 上へ移動 Network Connection = ネットワーク接続 NEW DATA = 新しいデータ No = いいえ +None = 無し ObtainingIP = IPアドレスの取得中。 \nしばらくお待ちください... OK = OK Old savedata detected = 古いセーブデータを検出しました @@ -393,6 +399,9 @@ Supported = サポートしています There is no data = データがありません。 Toggle All = 全て反転する Toggle List = 切り替えリスト +Top Center = 上中央 +Top Left = 左上 +Top Right = 右上 Unsupported = サポートしていません Username = Username When you save, it will load on a PSP, but not an older PPSSPP = 保存すると、PSPではロードできますが古いPPSSPPではロードできません @@ -766,11 +775,6 @@ AdHoc Server = アドホックサーバ AdhocServer Failed to Bind Port = アドホックサーバーがポートのバインドに失敗しました AM: Data from Unknown Port = AM: 不明なポートからのデータ Auto = 自動 -Bottom Center = 下中央 -Bottom Left = 左下 -Bottom Right = 右下 -Center Left = 左中央 -Center Right = 右中央 Change Mac Address = MACアドレスを変更する Change proAdhocServer Address = PROアドホックサーバのIPアドレスを変更する (localhost = multiple instances) ChangeMacSaveConfirm = 新しいMACアドレスを生成しますか? @@ -804,7 +808,6 @@ Invalid IP or hostname = 無効なIPまたはホスト名 Minimum Timeout = 最小タイムアウト (低遅延をオーバーライド、単位ms) Misc = その他 (デフォルト = PSP互換) Network Initialized = ネットワークを初期化 -None = 無し Please change your Port Offset = ポートオフセットを変更してください Port offset = ポートオフセット (0 = PSP互換) Open PPSSPP Multiplayer Wiki Page = PPSSPPのアドホック接続Wikiページを開く (英語) @@ -818,9 +821,6 @@ QuickChat = クイックチャット Randomize = Randomize Send = 送信する Send Discord Presence information = Discordのプレゼンス情報を送信する -Top Center = 上中央 -Top Left = 左上 -Top Right = 右上 Unable to find UPnP device = UPnPデバイスがみつかりません UPnP (port-forwarding) = UPnP(ポートフォワーディング) UPnP need to be reinitialized = UPnPの再初期化が必要です diff --git a/assets/lang/jv_ID.ini b/assets/lang/jv_ID.ini index dd994bd569..11d42c3c40 100644 --- a/assets/lang/jv_ID.ini +++ b/assets/lang/jv_ID.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Bali +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Mbatalake Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Peleh folder penyimpenan PPSSPP @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Koneksi Jaringan NEW DATA = DATA ANYAR No = Ora +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = He.eh Old savedata detected = Simpenan lawas terdeteksi @@ -393,6 +399,9 @@ Supported = Supported There is no data = Ora ana data. Toggle All = Peleh kabeh Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Nalika nyimpen, iku bakal mbukak ing PSP,nanging ora PPSSPP lawas @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Ubah alamat MAC Change proAdhocServer Address = Ubah alamat pelayan IP PRO ad hoc (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Jaringan diinisiasi -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/ko_KR.ini b/assets/lang/ko_KR.ini index 423b147c8e..7b2616f055 100644 --- a/assets/lang/ko_KR.ini +++ b/assets/lang/ko_KR.ini @@ -316,8 +316,13 @@ VFPU = VFPU * PSP res = * PSP 리소스 Active = 활성화 Back = 뒤로가기 +Bottom Center = 하단 중앙 +Bottom Left = 하단 왼쪽 +Bottom Right = 하단 오른쪽 Cancel = 취소 Center = 중앙 +Center Left = 중앙 왼쪽 +Center Right = 중앙 오른쪽 Changing this setting requires PPSSPP to restart. = 이 설정을 변경하려면 PPSSPP를 다시 시작해야 합니다. Channel: = 채널: Choose PPSSPP save folder = PPSSPP 저장 폴더 선택 @@ -366,6 +371,7 @@ Move Down = 아래로 이동 Network Connection = 네트워크 연결 NEW DATA = 새로운 데이터 No = 아니오 +None = 없음 ObtainingIP = IP 주소를 가져오는 중입니다.\n잠시만 기다려 주세요... OK = 확인 Old savedata detected = 오래된 저장 데이터 감지됨 @@ -394,6 +400,9 @@ Supported = 지원 There is no data = 데이터가 없습니다. Toggle All = 모두 토글 Toggle List = 토글 목록 +Top Center = 상단 중앙 +Top Left = 상단 왼쪽 +Top Right = 상단 오른쪽 Unsupported = 미지원 Username = 사용자이름 When you save, it will load on a PSP, but not an older PPSSPP = 저장하면 PSP에 불러오지만 이전 PPSSPP에는 부러오지 않습니다. @@ -767,11 +776,6 @@ AdHoc Server = Ad hoc 서버 AdhocServer Failed to Bind Port = Ad Hoc 서버가 포트를 바인딩하지 못했습니다. AM: Data from Unknown Port = AM: 알 수 없는 포트의 데이터 Auto = 자동 -Bottom Center = 하단 중앙 -Bottom Left = 하단 왼쪽 -Bottom Right = 하단 오른쪽 -Center Left = 중앙 왼쪽 -Center Right = 중앙 오른쪽 Change Mac Address = MAC 주소 변경 ChangeMacSaveConfirm = 새로운 MAC 주소를 생성하겠습니까? ChangeMacSaveWarning = 일부 게임은 저장 데이터를 불러올 때 MAC 주소를 확인하므로 이전 저장이 손상될 수 있습니다. @@ -805,7 +809,6 @@ Invalid IP or hostname = 잘못된 IP 또는 호스트 이름 Minimum Timeout = 최소 시간 초과 (ms 단위로 재정의, 0 = 기본값) Misc = 기타 (기본값 = PSP 호환성) Network Initialized = 네트워크 초기화 -None = 없음 Please change your Port Offset = 포트 오프셋을 변경하세요. Port offset = 포트 오프셋 (0 = PSP 호환성) Open PPSSPP Multiplayer Wiki Page = PPSSPP 멀티플레이어 위키 페이지 열기 @@ -819,9 +822,6 @@ QuickChat = 빠른 채팅 Randomize = Randomize Send = 보내기 Send Discord Presence information = 디스코드 "리치 프레즌스" 정보 보내기 -Top Center = 상단 중앙 -Top Left = 상단 왼쪽 -Top Right = 상단 오른쪽 Unable to find UPnP device = UPnP 장치를 찾을 수 없습니다. UPnP (port-forwarding) = UPnP (포트 포워딩) UPnP need to be reinitialized = UPnP를 다시 초기화해야 합니다. diff --git a/assets/lang/lo_LA.ini b/assets/lang/lo_LA.ini index c4aa049711..d5135f3d7e 100644 --- a/assets/lang/lo_LA.ini +++ b/assets/lang/lo_LA.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * ຂະໜາດຈໍ PSP Active = Active Back = ຍ້ອນກັບ +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = ຍົກເລີກ Center = ທາງກາງ +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = ເລືອກໂຟນເດີ້ເຊບ PPSSPP @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = ການເຊື່ອມຕໍ່ເຄື່ອຂ່າຍ NEW DATA = ຂໍ້ມູນໃໝ່ No = ບໍ່ແມ່ນ +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = ຕົກລົງ Old savedata detected = ກວດພົບຂໍ້ມູນເຊບເກົ່າ @@ -393,6 +399,9 @@ Supported = Supported There is no data = ບໍ່ມີຂໍ້ມູນ. Toggle All = ເລືອກທັງໝົດ Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = ເມື່ອເຈົ້າເຊບເກມ, ຈະສາມາດນຳໄປໂຫຼດເທິງເຄື່ອງ PSP ໄດ້\nແຕ່ບໍ່ແມ່ນສຳລັບ PPSSPP ທີ່ເກົ່າກວ່າ @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = ປ່ຽນຄ່າທີ່ຢູ່ MAC Change proAdhocServer Address = ປ່ຽນໄອພີທີ່ຢູ່ຂອງເຊີບເວີ Pro Adhoc (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = ເລີ່ມການເຮັດວຽກລະບົບເຄື່ອຂ່າຍແລ້ວ -None = None Please change your Port Offset = Please change your port offset Port offset = ພອຣ໌ດຊົດເຊີຍ (0 = ຄ່າເລີ່ມຕົ້ນຂອງ PSP) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/lt-LT.ini b/assets/lang/lt-LT.ini index 1eca885043..ef4daaf46b 100644 --- a/assets/lang/lt-LT.ini +++ b/assets/lang/lt-LT.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Atgal +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Atšaukti Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Pasirinkite PPSSPP išsaugojimo aplanką @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Interneto tinklas NEW DATA = NAUJI DUOMENYS No = Ne +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = Gerai Old savedata detected = Aptikti seniau išsaugoti duomenys @@ -393,6 +399,9 @@ Supported = Supported There is no data = Čia nėra jokių duomenų. Toggle All = Perjungti visus Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Change MAC address Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/ms_MY.ini b/assets/lang/ms_MY.ini index da3b015a40..bf75fe60c2 100644 --- a/assets/lang/ms_MY.ini +++ b/assets/lang/ms_MY.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Balik +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Batal Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Network Connection NEW DATA = DATA BAHARU No = Tidak +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Old savedata detected @@ -393,6 +399,9 @@ Supported = Supported There is no data = Tiada data disini. Toggle All = Periksa semua Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Tidak disokong Username = Username When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Change MAC address Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/nl_NL.ini b/assets/lang/nl_NL.ini index 125a0f9d15..05ccf32aa3 100644 --- a/assets/lang/nl_NL.ini +++ b/assets/lang/nl_NL.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP-resolutie Active = Active Back = Terug +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Annuleren Center = Centreren +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = PPSSPP-opslagmap selecteren @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Netwerkverbinding NEW DATA = NIEUWE DATA No = Nee +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Oude savedata gedetecteerd @@ -393,6 +399,9 @@ Supported = Ondersteund There is no data = Er zijn geen data. Toggle All = Alles schakelen Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Niet ondersteund Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Als u opslaat kunt u de opgeslagen data laden op\nPSP-systemen, maar niet op oudere PPSSPP-versies. @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = MAC-adres wijzigen Change proAdhocServer Address = IP-adres van PRO Ad hoc-server wijzigen (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Netwerk geïnitialiseerd -None = None Please change your Port Offset = Please change your port offset Port offset = Poortoffset (0 = PSP-compatibiliteit) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/no_NO.ini b/assets/lang/no_NO.ini index 7a325e365e..69199fd326 100644 --- a/assets/lang/no_NO.ini +++ b/assets/lang/no_NO.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Tilbake +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Cancel Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Choose PPSSPP save folder @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Network Connection NEW DATA = NEW DATA No = Nei +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Old savedata detected @@ -393,6 +399,9 @@ Supported = Supported There is no data = Det finnes ingen data. Toggle All = Toggle all Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Change MAC address Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/pl_PL.ini b/assets/lang/pl_PL.ini index 64740920f8..a38b7e3077 100644 --- a/assets/lang/pl_PL.ini +++ b/assets/lang/pl_PL.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * rozdz. PSP Active = Aktywny Back = Powrót +Bottom Center = Dolny środek +Bottom Left = Dolne lewo +Bottom Right = Dolne prawo Cancel = Anuluj Center = Wyśrodkuj +Center Left = Środkowe lewo +Center Right = Środkowe prawo Changing this setting requires PPSSPP to restart. = Zmiana tego ustawienia wymaga ponownego uruchomienia PPSSPP. Channel: = Kanał: Choose PPSSPP save folder = Wybierz folder do zapisu stanów PPSSPP @@ -365,6 +370,7 @@ Move Up = Przenieś w górę Network Connection = Połączenie sieciowe NEW DATA = NOWE DANE No = Nie +None = Nic ObtainingIP = Uzyskiwanie adresu IP.\nProszę czekać... OK = OK Old savedata detected = Wykryto stary zapis gry @@ -393,6 +399,9 @@ Supported = Wspierane There is no data = Brak danych. Toggle All = Wybierz wszystko Toggle List = Wybierz listę +Top Center = Górny środek +Top Left = Górne lewo +Top Right = Górne prawo Unsupported = Niewspierane Username = Nazwa użytkownika When you save, it will load on a PSP, but not an older PPSSPP = Jeśli zapiszesz, PSP wczyta ten zapis gry, ale starszy PPSSPP już nie. @@ -799,11 +808,6 @@ AdHoc Server = Serwer Ad-Hoc AdhocServer Failed to Bind Port = Błąd przy przypisywaniu portu na serwerze Ad-Hoc AM: Data from Unknown Port = AM: Dane z nieznanego portu Auto = Automatycznie -Bottom Center = Dolny środek -Bottom Left = Dolne lewo -Bottom Right = Dolne prawo -Center Left = Środkowe lewo -Center Right = Środkowe prawo Change Mac Address = Zmień adres MAC Change proAdhocServer Address = Zmień adres IP serwera PRO dla trybu ad hoc (localhost = multiple instances) ChangeMacSaveConfirm = Wygenerować nowy adres MAC? @@ -837,7 +841,6 @@ Invalid IP or hostname = Niepoprawny adres IP lub nazwa hosta Minimum Timeout = Minimalny czas oczekiwania (podaj w ms, domyślnie 0) Misc = Inne (domyślnie = Kompatybilność z PSP) Network Initialized = Sieć została zainicjowana -None = Nic Please change your Port Offset = Proszę zmienić port Port offset = Zmień port (0 = zgodne z PSP) Open PPSSPP Multiplayer Wiki Page = Przejdź do strony PPSSPP pomocy dla wielu graczy. @@ -851,9 +854,6 @@ QuickChat = Szybki czat Randomize = Randomize Send = Wyślij Send Discord Presence information = Przesyłaj informacje Discord Rich Presence -Top Center = Górny środek -Top Left = Górne lewo -Top Right = Górne prawo Unable to find UPnP device = Nie udało się zlokalizować urządzenia UPnP UPnP (port-forwarding) = UPnP (przekierowanie portów) UPnP need to be reinitialized = Należy ponownie zainicjować UPnP diff --git a/assets/lang/pt_BR.ini b/assets/lang/pt_BR.ini index 1f77f97d20..a476532e69 100644 --- a/assets/lang/pt_BR.ini +++ b/assets/lang/pt_BR.ini @@ -339,8 +339,13 @@ VFPU = VFPU * PSP res = * Resolução do PSP Active = Ativo Back = Voltar +Bottom Center = Centro do rodapé +Bottom Left = A esquerda do rodapé +Bottom Right = A direita do rodapé Cancel = Cancelar Center = Centro +Center Left = Centralizar a esquerda +Center Right = Centralizar a direita Changing this setting requires PPSSPP to restart. = A alteração dessa configuração requer a reinicialização do PPSSPP. Channel: = Canal: Choose PPSSPP save folder = Escolha a pasta dos saves do PPSSPP @@ -389,6 +394,7 @@ Move Down = Mover pra Baixo Network Connection = Conexão de rede NEW DATA = NOVOS DADOS No = Não +None = Nenhum ObtainingIP = Obtendo endereço de IP.\nPor favor espere... OK = Ok Old savedata detected = Dados antigos do save detectados @@ -417,6 +423,9 @@ Supported = Suportado There is no data = Não há dados. Toggle All = Alternar todos Toggle List = Alternar lista +Top Center = Centro superior +Top Left = Superior esquerdo +Top Right = Superior direito Unsupported = Não suportado Username = Nome de usuário When you save, it will load on a PSP, but not an older PPSSPP = Quando você salvar ele carregará num PSP mas não num PPSSPP mais antigo @@ -790,11 +799,6 @@ AdHoc Server = Servidor Ad-hoc AdhocServer Failed to Bind Port = O servidor Ad-hoc falhou em se conectar a porta AM: Data from Unknown Port = AM: Dados de uma Porta Desconhecida Auto = Auto -Bottom Center = Centro do rodapé -Bottom Left = A esquerda do rodapé -Bottom Right = A direita do rodapé -Center Left = Centralizar a esquerda -Center Right = Centralizar a direita Change Mac Address = Mudar o endereço do MAC ChangeMacSaveConfirm = Gerar um novo endereço pro MAC? ChangeMacSaveWarning = Alguns jogos verificam o endereço do MAC quando carregam os dados do save então isto pode quebrar os saves antigos. @@ -828,7 +832,6 @@ Invalid IP or hostname = IP ou nome do hospedeiro inválido Minimum Timeout = Tempo mínimo pra encerrar (substituição em ms, 0 = padrão) Misc = Miscelânea (padrão = compatibilidade com o PSP) Network Initialized = Rede inicializada -None = Nenhum Please change your Port Offset = Por favor mude seu deslocamento da porta Port offset = Deslocamento da porta (0 = Compatibilidade com o PSP) Open PPSSPP Multiplayer Wiki Page = Abrir a Página do Multiplayer do Wiki do PPSSPP @@ -842,9 +845,6 @@ QuickChat = Bate-papo rápido Randomize = Randomize Send = Enviar Send Discord Presence information = Enviar informação da "Presença Rica" no Discord -Top Center = Centro superior -Top Left = Superior esquerdo -Top Right = Superior direito Unable to find UPnP device = Incapaz de achar o dispositivo UPnP UPnP (port-forwarding) = UPnP (abertura das portas) UPnP need to be reinitialized = O UPnP precisa ser reinicializado diff --git a/assets/lang/pt_PT.ini b/assets/lang/pt_PT.ini index 41d1bd57fd..1f9da3e337 100644 --- a/assets/lang/pt_PT.ini +++ b/assets/lang/pt_PT.ini @@ -339,8 +339,13 @@ VFPU = VFPU * PSP res = * Resolução do PSP Active = Ativo Back = Voltar +Bottom Center = Centro do rodapé +Bottom Left = A esquerda do rodapé +Bottom Right = A direita do rodapé Cancel = Cancelar Center = Centro +Center Left = Centralizar a esquerda +Center Right = Centralizar a direita Changing this setting requires PPSSPP to restart. = A alteração dessa configuração requer a reinicialização do PPSSPP. Channel: = Canal: Choose PPSSPP save folder = Escolha a pasta de save do PPSSPP @@ -389,6 +394,7 @@ Move Up = Move Up Network Connection = Conexão de rede NEW DATA = NOVOS DADOS No = Não +None = Nenhum ObtainingIP = Obtendo o Endereço de IP.\nPor favor espere... OK = Ok Old savedata detected = Dados antigos do save detectados @@ -417,6 +423,9 @@ Supported = Suportado There is no data = Não existem dados. Toggle All = Alternar todos Toggle List = Alternar lista +Top Center = Centro superior +Top Left = Superior esquerdo +Top Right = Superior direito Unsupported = Não suportado Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Quando gravar, os dados vão carregar numa PSP, mas não num PPSSPP antigo. @@ -790,11 +799,6 @@ AdHoc Server = Servidor Ad-hoc AdhocServer Failed to Bind Port = O servidor Ad-hoc falhou a conectar-se à porta. AM: Data from Unknown Port = AM: Dados de uma Porta Desconhecida. Auto = Automático -Bottom Center = Centro do rodapé -Bottom Left = A esquerda do rodapé -Bottom Right = A direita do rodapé -Center Left = Centralizar a esquerda -Center Right = Centralizar a direita Change Mac Address = Mudar Endereço de MAC Change proAdhocServer Address = Mudar o Endereço de IP do servidor Ad Hoc PRO (localhost = múltiplas instâncias) ChangeMacSaveConfirm = Generate a new MAC address? @@ -828,7 +832,6 @@ Invalid IP or hostname = IP ou nome do Hospedeiro inválido Minimum Timeout = Tempo mínimo para encerrar (substituição em ms, 0 = padrão) Misc = Miscelânea (padrão = compatibilidade com o PSP) Network Initialized = Rede inicializada -None = Nenhum Please change your Port Offset = Por favor mude o seu deslocamento da porta Port offset = Deslocamento da porta (0 = Compatibilidade com o PSP) Open PPSSPP Multiplayer Wiki Page = Abrir a Página do Multiplayer da Wiki do PPSSPP @@ -842,9 +845,6 @@ QuickChat = Bate-papo rápido Randomize = Randomize Send = Enviar Send Discord Presence information = Enviar informação de "Presença Rica" no Discord -Top Center = Centro superior -Top Left = Superior esquerdo -Top Right = Superior direito Unable to find UPnP device = Não é possível encontrar o dispositivo UPnP UPnP (port-forwarding) = UPnP (abertura das portas) UPnP need to be reinitialized = O UPnP precisa ser reiniciado diff --git a/assets/lang/ro_RO.ini b/assets/lang/ro_RO.ini index 5bd6fd09c8..8355f16c1b 100644 --- a/assets/lang/ro_RO.ini +++ b/assets/lang/ro_RO.ini @@ -316,8 +316,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Înapoi +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Ignoră Center = Center +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Alege folder pt. salvări PPSSPP @@ -366,6 +371,7 @@ Move Up = Move Up Network Connection = Conexiune la rețea NEW DATA = DATE NOI No = Nu +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Date vechi salvate detectate @@ -394,6 +400,9 @@ Supported = Supported There is no data = Salvare inexistentă Toggle All = Schimbă tot Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Când salvezi, va încărca pe un PSP, dar nu un PPSSPP mai vechi @@ -796,11 +805,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Schimbă adresa MAC Change proAdhocServer Address = Schimbă adresa IP a serverului PRO ad hoc (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -834,7 +838,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Rețea inițializată -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -848,9 +851,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/ru_RU.ini b/assets/lang/ru_RU.ini index 8c14acd766..1f656a96d1 100644 --- a/assets/lang/ru_RU.ini +++ b/assets/lang/ru_RU.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * разр. PSP Active = Активно Back = Назад +Bottom Center = Внизу в центре +Bottom Left = Внизу слева +Bottom Right = Внизу справа Cancel = Отмена Center = По центру +Center Left = В центре слева +Center Right = В центре справа Changing this setting requires PPSSPP to restart. = Изменение этого параметра требует перезапуска PPSSPP. Channel: = Канал: Choose PPSSPP save folder = Выбрать папку с сохранениями @@ -365,6 +370,7 @@ Move Up = Сдвинуть вниз Network Connection = Сетевое подключение NEW DATA = Новое сохранение No = Нет +None = Нет ObtainingIP = Получение IP-адреса.\nПожалуйста, подождите... OK = OK Old savedata detected = Старый формат сохранений обнаружен @@ -393,6 +399,9 @@ Supported = Поддерживается There is no data = Нет данных Toggle All = Переключить все Toggle List = Переключить список +Top Center = Вверху в центре +Top Left = Вверху слева +Top Right = Вверу справа Unsupported = Не поддерживается Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Когда вы сохранитесь, оно может быть загружено на PSP, но не на старой версии PPSSPP @@ -766,11 +775,6 @@ AdHoc Server = Ad-hoc сервер AdhocServer Failed to Bind Port = Не удалось привязать порт ad-hoc сервера AM: Data from Unknown Port = AM: Данные от неизвестного порта Auto = Авто -Bottom Center = Внизу в центре -Bottom Left = Внизу слева -Bottom Right = Внизу справа -Center Left = В центре слева -Center Right = В центре справа Change Mac Address = Изменить MAC-адрес Change proAdhocServer Address = Изменить IP-адрес ad-hoc сервера (localhost = множество экземпляров) ChangeMacSaveConfirm = Сгенерировать новый MAC-адрес? @@ -804,7 +808,6 @@ Invalid IP or hostname = Некорректный IP или имя хоста Minimum Timeout = Минимальный таймаут (задержка в мс, 0 = по умолчанию) Misc = Прочее (по умолчанию = совместимость с PSP) Network Initialized = Сеть активирована -None = Нет Please change your Port Offset = Пожалуйста, измените ваше смещение порта Port offset = Смещение порта (0 = совместимость с PSP) Open PPSSPP Multiplayer Wiki Page = Открыть wiki-страницу мультиплеера PPSSPP @@ -818,9 +821,6 @@ QuickChat = Быстрый чат Randomize = Randomize Send = Отправить Send Discord Presence information = Отправлять информацию об игре в Discord -Top Center = Вверху в центре -Top Left = Вверху слева -Top Right = Вверу справа Unable to find UPnP device = Невозможно найти устройство UPnP UPnP (port-forwarding) = UPnP (проброс портов) UPnP need to be reinitialized = Необходимо перезапустить UPnP diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index f5d8b9953a..f567e48406 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Aktiv Back = Tillbaka +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Avbryt Center = Centrera +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Att ändra denna inställning kräver att PPSSPP startas om. Channel: = Kanal: Choose PPSSPP save folder = Välj folder att spara i @@ -366,6 +371,7 @@ Move Up = Flytta upp Network Connection = Nätverksuppkoppling NEW DATA = NY DATA No = Nej +None = None ObtainingIP = Hämtar IP-address.\nVänta... OK = OK Old savedata detected = Gammal savedata upptäckt @@ -394,6 +400,9 @@ Supported = Stödd There is no data = Det finns ingen data. Toggle All = Vänd alla Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Stöds ej Username = Användarnamn When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -767,11 +776,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Ändra Mac-adress Change proAdhocServer Address = Ändra proAdhocServer-adress (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -805,7 +809,6 @@ Invalid IP or hostname = Ogiltigt IP eller hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Nätverk initierat -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -819,9 +822,6 @@ QuickChat = Snabbchat Randomize = Randomize Send = Skicka Send Discord Presence information = Visa Discord "Rich Presence"-information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/tg_PH.ini b/assets/lang/tg_PH.ini index a3b2b8678d..ff72bcf87e 100644 --- a/assets/lang/tg_PH.ini +++ b/assets/lang/tg_PH.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Active Back = Bumalik +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Kanselahin Center = Gitna +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Piliin ang PPSSPP save folder @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Network Connection NEW DATA = BAGONG DATOS No = Hindi +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = Sige Old savedata detected = Nahanap ang lumang savedata @@ -393,6 +399,9 @@ Supported = Supported There is no data = Walang Datos Toggle All = Toggle all Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Unsupported Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Kapag iyong na-i save, gagana ito mismo sa PSP\npero hindi sa lumang bersyon ng PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Baguhin ang MAC Address Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Network initialized -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/th_TH.ini b/assets/lang/th_TH.ini index 97c3db01b8..b91f659068 100644 --- a/assets/lang/th_TH.ini +++ b/assets/lang/th_TH.ini @@ -322,8 +322,13 @@ VFPU = VFPU * PSP res = * ขนาดจอ PSP Active = เปิดการทำงาน Back = ย้อนกลับ +Bottom Center = มุมล่างกลางจอ +Bottom Left = มุมล่างซ้ายจอ +Bottom Right = มุมล่างขวาจอ Cancel = ยกเลิก Center = กึ่งกลาง +Center Left = มุมกลางซ้ายจอ +Center Right = มุมกลางขวาจอ Changing this setting requires PPSSPP to restart. = การเปลี่ยนการตั้งค่านี้จำเป็นต้องรีสตาร์ท PPSSPP Channel: = แชลนอล: Choose PPSSPP save folder = เลือกโฟลเดอร์สำหรับเก็บข้อมูลเซฟของ PPSSPP @@ -372,6 +377,7 @@ Move Up = ย้ายขึ้นบน Network Connection = การเชื่อมต่อเครือข่าย NEW DATA = ข้อมูลใหม่ No = ไม่ใช่ +None = ไม่มี ObtainingIP = กำลังรับไอพี\nโปรดรอสักครู่... OK = ตกลง Old savedata detected = ตรวจพบข้อมูลเซฟเก่า @@ -400,6 +406,9 @@ Supported = รองรับ There is no data = ไม่มีข้อมูล Toggle All = ทั้งหมด Toggle List = รายการ +Top Center = มุมบนกลางจอ +Top Left = มุมบนซ้ายจอ +Top Right = มุมบนขวาจอ Unsupported = ไม่รองรับ Username = ชื่อผู้ใช้งาน When you save, it will load on a PSP, but not an older PPSSPP = เซฟเกมของคุณนั้น สามารถนำไปโหลดผ่านบนเครื่อง PSP ได้\nแต่นำไปใช้กับ PPSSPP ที่เวอร์ชั่นเก่ากว่าไม่ได้ @@ -803,11 +812,6 @@ AdHoc Server = เซิร์ฟเวอร์ Adhoc AdhocServer Failed to Bind Port = เซิร์ฟเวอร์ Adhoc ล้มเหลวในการเชื่อมโยงพอร์ต AM: Data from Unknown Port = AM: ข้อมูลมาจากพอร์ตที่ไม่รู้จัก Auto = อัตโนมัติ -Bottom Center = มุมล่างกลางจอ -Bottom Left = มุมล่างซ้ายจอ -Bottom Right = มุมล่างขวาจอ -Center Left = มุมกลางซ้ายจอ -Center Right = มุมกลางขวาจอ Change Mac Address = เปลี่ยนค่าที่อยู่ Mac Change proAdhocServer Address = เปลี่ยนไอพีที่อยู่ของเซิร์ฟเวอร์ Pro Adhoc (localhost = แลนในเครื่องเดียวกัน) ChangeMacSaveConfirm = ต้องการสร้างค่า MAC address ใหม่? @@ -841,7 +845,6 @@ Invalid IP or hostname = ไอพี หรือ ชื่อโฮสต์ Minimum Timeout = เวลาขั้นต่ำที่ใช้เชื่อมต่อ (แทนที่ค่าความหน่วงในมิลลิวินาที) Misc = อื่นๆ (ค่าดั้งเดิม = ดีสุดแล้ว) Network Initialized = เริ่มการทำงานของเครือข่ายเรียบร้อย -None = ไม่มี Please change your Port Offset = กรุณาเปลี่ยนค่าของพอร์ตชดเชย Port offset = พอร์ตชดเชย (0 = ค่าเริ่มต้นของ PSP) Open PPSSPP Multiplayer Wiki Page = ไปที่หน้าเว็บ PPSSPP Ad-Hoc Wiki @@ -855,9 +858,6 @@ QuickChat = แชทด่วน Randomize = กดสุ่มค่า Send = ส่ง Send Discord Presence information = แสดงชื่อเกมที่กำลังเล่นอยู่ไปยังแอพ Discord -Top Center = มุมบนกลางจอ -Top Left = มุมบนซ้ายจอ -Top Right = มุมบนขวาจอ Unable to find UPnP device = ไม่พบอุปกรณ์ UPnP UPnP (port-forwarding) = โพรโตคอล UPnP (ฟอร์เวิร์ด พอร์ต อัตโนมัติ) UPnP need to be reinitialized = UPnP จำเป็นต้องเริ่มการทำงานใหม่ diff --git a/assets/lang/tr_TR.ini b/assets/lang/tr_TR.ini index 2aa9d35cd6..f880929e75 100644 --- a/assets/lang/tr_TR.ini +++ b/assets/lang/tr_TR.ini @@ -317,8 +317,13 @@ VFPU = VFPU * PSP res = * PSP Çözünürlüğü Active = Etkin Back = Geri +Bottom Center = Orta alt +Bottom Left = Sol alt +Bottom Right = Sağ alt Cancel = İptal Center = Merkez +Center Left = Sol orta +Center Right = Sağ orta Changing this setting requires PPSSPP to restart. = Bu ayarın değiştirilmesi, PPSSPP'nin yeniden başlatılmasını gerektirir. Channel: = Kanal: Choose PPSSPP save folder = PPSSPP kayıt dosyası seç @@ -367,6 +372,7 @@ Move Up = Move Up Network Connection = Ağ Bağlantısı NEW DATA = YENİ VERİ No = Hayır +None = Hiçbiri ObtainingIP = IP adresi alınıyor.\nLütfen bekleyin... OK = Tamam Old savedata detected = Eski kayıt bulundu. @@ -395,6 +401,9 @@ Supported = Destekleniyor There is no data = Veri yok. Toggle All = Hepsini değiştir Toggle List = Listeyi değiştir +Top Center = Orta üst +Top Left = Sol üst +Top Right = Sağ üst Unsupported = Desteklenmiyor Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Kayıt aldığında almış olduğunuz kayıt PSP'de yüklenebilecek ama eski bir PPSSPP sürümünde yüklenemeyecek. @@ -797,11 +806,6 @@ AdHoc Server = Ad hoc sunucusu AdhocServer Failed to Bind Port = Ad hoc sunucusu portu bağlayamadı AM: Data from Unknown Port = AM: Bilinmeyen Porttan Veri Auto = Otomatik -Bottom Center = Orta alt -Bottom Left = Sol alt -Bottom Right = Sağ alt -Center Left = Sol orta -Center Right = Sağ orta Change Mac Address = MAC adresini değiştir Change proAdhocServer Address = PRO Ad Hoc Server IP adresini değiştir (localhost = çoklu örnek) ChangeMacSaveConfirm = Generate a new MAC address? @@ -835,7 +839,6 @@ Invalid IP or hostname = Geçersiz IP veya host adı Minimum Timeout = En düşük zaman aşımı (ms cinsinden geçersiz kılma, 0 = varsayılan) Misc = Çeşitli (varsayılan = PSP uyumluluğu) Network Initialized = Network Switch -None = Hiçbiri Please change your Port Offset = Lütfen Port Offset'i değiştirin Port offset = Port offset(0 = PSP uyumluluğu) Open PPSSPP Multiplayer Wiki Page = PPSSPP Ad-Hoc Wiki Sayfası @@ -849,9 +852,6 @@ QuickChat = Hızlı sohbet Randomize = Randomize Send = Gönder Send Discord Presence information = Discord "Rich Presence" bilgisi gönder -Top Center = Orta üst -Top Left = Sol üst -Top Right = Sağ üst Unable to find UPnP device = UPnP cihazı bulunamıyor UPnP (port-forwarding) = UPnP (port yönlendirme) UPnP need to be reinitialized = UPnP'nin yeniden başlatılması gerekiyor diff --git a/assets/lang/uk_UA.ini b/assets/lang/uk_UA.ini index 674833929e..0f77d1cbc1 100644 --- a/assets/lang/uk_UA.ini +++ b/assets/lang/uk_UA.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * дозв. PSP Active = Активно Back = Назад +Bottom Center = Знизу центрально +Bottom Left = Знизу ліворуч +Bottom Right = Знизу праворуч Cancel = Відміна Center = По центру +Center Left = Центр ліворуч +Center Right = Центр праворуч Changing this setting requires PPSSPP to restart. = Зміна буферизації графічних команд вимагає перезавантажити PPSSPP. Перезавантажити зараз? Channel: = Channel: Choose PPSSPP save folder = Виберіть папку для збереження @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Підключення до мережі NEW DATA = Нове збереження No = Ні +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Виявлено старий формат збережень @@ -393,6 +399,9 @@ Supported = Підтримується There is no data = Немає даних Toggle All = Переключити все Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = Не підтримується Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Коли ви збережетесь, це може завантажуватись на PSP, але не старій версії PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Знизу центрально -Bottom Left = Знизу ліворуч -Bottom Right = Знизу праворуч -Center Left = Центр ліворуч -Center Right = Центр праворуч Change Mac Address = Змінити Mac-адресу Change proAdhocServer Address = Спеціальна адреса серверу (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Недійсне IP або ім'я хоста Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Мережа ініціалізована -None = None Please change your Port Offset = Please change your port offset Port offset = Зсув порту (0 = сумісність з PSP) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Швидкий чат Randomize = Randomize Send = Надіслати Send Discord Presence information = Відправляти інформацію про гру в Discord -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/vi_VN.ini b/assets/lang/vi_VN.ini index 7d10198c86..fd8c4f06ec 100644 --- a/assets/lang/vi_VN.ini +++ b/assets/lang/vi_VN.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP như thật Active = Hoạt động Back = Trở lại +Bottom Center = Bottom center +Bottom Left = Bottom left +Bottom Right = Bottom right Cancel = Hủy Center = Trung tâm +Center Left = Center left +Center Right = Center right Changing this setting requires PPSSPP to restart. = Changing this setting requires PPSSPP to restart. Channel: = Channel: Choose PPSSPP save folder = Chọn thư mục save của PPSSPP @@ -365,6 +370,7 @@ Move Up = Move Up Network Connection = Kết nối mạng NEW DATA = DỮ LIỆU MỚI No = Không +None = None ObtainingIP = Obtaining IP address.\nPlease wait... OK = OK Old savedata detected = Phát hiện dữ liệu cũ @@ -393,6 +399,9 @@ Supported = Được hỗ trợ There is no data = Không có dữ liệu. Toggle All = Tất cả Toggle List = Toggle list +Top Center = Top center +Top Left = Top left +Top Right = Top right Unsupported = không hổ trợ Username = Username When you save, it will load on a PSP, but not an older PPSSPP = Khi bạn save, nó có thể load được trên máy PSP, nhưng lại không thể load được trên PPSSPP phiên bản cũ @@ -795,11 +804,6 @@ AdHoc Server = Ad hoc server AdhocServer Failed to Bind Port = Ad hoc server failed to bind port AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right -Center Left = Center left -Center Right = Center right Change Mac Address = Đổi địa chỉ MAC Change proAdhocServer Address = Change PRO ad hoc server IP address (localhost = multiple instances) ChangeMacSaveConfirm = Generate a new MAC address? @@ -833,7 +837,6 @@ Invalid IP or hostname = Invalid IP or hostname Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility) Network Initialized = Mạng đã được khởi tạo -None = None Please change your Port Offset = Please change your port offset Port offset = Port offset(0 = PSP compatibility) Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page @@ -847,9 +850,6 @@ QuickChat = Quick chat Randomize = Randomize Send = Send Send Discord Presence information = Send Discord "Rich Presence" information -Top Center = Top center -Top Left = Top left -Top Right = Top right Unable to find UPnP device = Unable to find UPnP device UPnP (port-forwarding) = UPnP (port forwarding) UPnP need to be reinitialized = UPnP need to be reinitialized diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index 2d616bc62a..d25bb7f341 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP分辨率 Active = 激活 Back = 返回 +Bottom Center = 正下方 +Bottom Left = 左下方 +Bottom Right = 右下方 Cancel = 取消 Center = 中心 +Center Left = 左中间 +Center Right = 右中间 Changing this setting requires PPSSPP to restart. = 设置生效需要重新启动 PPSSPP。 Channel: = 信道: Choose PPSSPP save folder = 选择PPSSPP存档的文件夹 @@ -365,6 +370,7 @@ Move Up = 移至上层 Network Connection = 网络连接 NEW DATA = 新建存档 No = 否 +None = 无 ObtainingIP = 获取IP地址.\n请稍候... OK = 确定 Old savedata detected = 检测到旧版本的存档数据 @@ -393,6 +399,9 @@ Supported = 支持 There is no data = 存档不存在。 Toggle All = 全选/不选 Toggle List = 显示列表 +Top Center = 正上方 +Top Left = 左上方 +Top Right = 右上方 Unsupported = 不支持 Username = 用户名 When you save, it will load on a PSP, but not an older PPSSPP = 当您保存后, 它只会在PSP实机上载入, 而不是旧版本的PPSSPP @@ -795,11 +804,6 @@ AdHoc Server = Ad Hoc服务器 AdhocServer Failed to Bind Port = 无法绑定Adhoc服务器端口 AM: Data from Unknown Port = AM:来自未知端口的数据 Auto = 自动 -Bottom Center = 正下方 -Bottom Left = 左下方 -Bottom Right = 右下方 -Center Left = 左中间 -Center Right = 右中间 Change Mac Address = 更改MAC地址 Change proAdhocServer Address = 更改 PRO Ad Hoc 服务器IP地址 ChangeMacSaveConfirm = 确认随机生成新的MAC地址? @@ -833,7 +837,6 @@ Invalid IP or hostname = IP或主机名是无效的 Minimum Timeout = 最小超时(以毫秒为单位,0=默认值) Misc = 其他(默认=兼容PSP) Network Initialized = 网络已初始化 -None = 无 Please change your Port Offset = 请更改您的端口偏移 Port offset = 端口偏移(0 = 兼容PSP) Open PPSSPP Multiplayer Wiki Page = PPSSPP多人联机Wiki页面 @@ -848,9 +851,6 @@ QuickChat = 快速聊天 Randomize = Randomize Send = 发送 Send Discord Presence information = 发送 Discord 在线状态 -Top Center = 正上方 -Top Left = 左上方 -Top Right = 右上方 Unable to find UPnP device = 找不到UPnP设备 UPnP (port-forwarding) = UPnP(端口转发) UPnP need to be reinitialized = UPnP 需要重新初始化 diff --git a/assets/lang/zh_TW.ini b/assets/lang/zh_TW.ini index c3e5be6690..4629c1d6c4 100644 --- a/assets/lang/zh_TW.ini +++ b/assets/lang/zh_TW.ini @@ -315,8 +315,13 @@ VFPU = VFPU * PSP res = * PSP 解析度 Active = 啟用 Back = 返回 +Bottom Center = 正下 +Bottom Left = 左下 +Bottom Right = 右下 Cancel = 取消 Center = 中心 +Center Left = 正左 +Center Right = 正右 Changing this setting requires PPSSPP to restart. = 更改此設置需要重新啟動 PPSSPP。 Channel: = 頻道: Choose PPSSPP save folder = 選擇 PPSSPP 存檔資料夾 @@ -365,6 +370,7 @@ Move Up = 上移 Network Connection = 網路連線 NEW DATA = 新存檔 No = 否 +None = 無 ObtainingIP = 正在取得 IP 位址\n請稍候… OK = 確定 Old savedata detected = 偵測到舊版存檔資料 @@ -393,6 +399,9 @@ Supported = 支援 There is no data = 沒有資料 Toggle All = 切換全部 Toggle List = 切換清單 +Top Center = 正上 +Top Left = 左上 +Top Right = 右上 Unsupported = 不支援 Username = 使用者名稱 When you save, it will load on a PSP, but not an older PPSSPP = 在您儲存後,它將會只能在 PSP 上載入,而非舊版 PPSSPP @@ -766,11 +775,6 @@ AdHoc Server = 臨機操作伺服器 AdhocServer Failed to Bind Port = 臨機操作伺服器無法繫結連接埠 AM: Data from Unknown Port = AM:來自不明連接埠的資料 Auto = 自動 -Bottom Center = 正下 -Bottom Left = 左下 -Bottom Right = 右下 -Center Left = 正左 -Center Right = 正右 Change Mac Address = 變更 MAC 位址 ChangeMacSaveConfirm = 產生新的 MAC 位址? ChangeMacSaveWarning = 某些遊戲在載入儲存資料時會驗證 MAC 位址,這可能會損毀舊存檔 @@ -804,7 +808,6 @@ Invalid IP or hostname = 無效的 IP 或主機名稱 Minimum Timeout = 最小逾時 (單位為毫秒,0 = 預設) Misc = 雜項 (預設 = PSP 相容性) Network Initialized = 網路已初始化 -None = 無 Please change your Port Offset = 請變更您的連接埠位移 Port offset = 連接埠位移 (0 = PSP 相容性) Open PPSSPP Multiplayer Wiki Page = 開啟 PPSSPP 多人遊戲維基頁面 @@ -818,9 +821,6 @@ QuickChat = 快速聊天 Randomize = Randomize Send = 傳送 Send Discord Presence information = 傳送 Discord「Rich Presence」資訊 -Top Center = 正上 -Top Left = 左上 -Top Right = 右上 Unable to find UPnP device = 找不到 UPnP 裝置 UPnP (port-forwarding) = UPnP (連接埠轉送) UPnP need to be reinitialized = UPnP 需要重新初始化 From 42ae0fd85c4ed06bad9869d51c2da2c60858deca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 31 Jul 2023 23:29:38 +0200 Subject: [PATCH 166/167] Update the sv_SE translation --- assets/lang/sv_SE.ini | 74 +++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index f567e48406..6a03562234 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -315,13 +315,13 @@ VFPU = VFPU * PSP res = * PSP res Active = Aktiv Back = Tillbaka -Bottom Center = Bottom center -Bottom Left = Bottom left -Bottom Right = Bottom right +Bottom Center = Underkant centrerad +Bottom Left = Underkant vänster +Bottom Right = Underkant höger Cancel = Avbryt Center = Centrera -Center Left = Center left -Center Right = Center right +Center Left = Centrerad vänster +Center Right = Centrerad höger Changing this setting requires PPSSPP to restart. = Att ändra denna inställning kräver att PPSSPP startas om. Channel: = Kanal: Choose PPSSPP save folder = Välj folder att spara i @@ -371,7 +371,7 @@ Move Up = Flytta upp Network Connection = Nätverksuppkoppling NEW DATA = NY DATA No = Nej -None = None +None = Ingen ObtainingIP = Hämtar IP-address.\nVänta... OK = OK Old savedata detected = Gammal savedata upptäckt @@ -400,9 +400,9 @@ Supported = Stödd There is no data = Det finns ingen data. Toggle All = Vänd alla Toggle List = Toggle list -Top Center = Top center -Top Left = Top left -Top Right = Top right +Top Center = Överkant centrerad +Top Left = Överkant vänster +Top Right = Överkant höger Unsupported = Stöds ej Username = Användarnamn When you save, it will load on a PSP, but not an older PPSSPP = When you save, it will load on a PSP, but not an older PPSSPP @@ -778,41 +778,41 @@ AM: Data from Unknown Port = AM: Data from Unknown Port Auto = Auto Change Mac Address = Ändra Mac-adress Change proAdhocServer Address = Ändra proAdhocServer-adress (localhost = multiple instances) -ChangeMacSaveConfirm = Generate a new MAC address? -ChangeMacSaveWarning = Some games verify the MAC address when loading savedata, so this may break old saves. +ChangeMacSaveConfirm = Generera en ny MAC-address? +ChangeMacSaveWarning = Vissa spel kollar MAC-address när de laddar savedata, så detta kan förstöra saves Chat = Chat -Chat Button Position = Chat button position -Chat Here = Chat here -Chat message = Chat message -Chat Screen Position = Chat screen position -Disconnected from AdhocServer = Disconnected from ad hoc server -DNS Error Resolving = DNS error resolving +Chat Button Position = Chatt-knappens position +Chat Here = Chatta här +Chat message = Chatt-meddelande +Chat Screen Position = Chatt-rutans position +Disconnected from AdhocServer = Bortkopplad från Adhoc-servern +DNS Error Resolving = DNS fel vid addressresolving Enable built-in PRO Adhoc Server = Enable built-in PRO Adhoc Server -Enable network chat = Enable network chat +Enable network chat = Nätverkschatt Enable networking = Nätverk/wlan (beta) Enable UPnP = Enable UPnP (need a few seconds to detect) -EnableQuickChat = Enable quick chat -Enter a new PSP nickname = Enter a new PSP nickname +EnableQuickChat = Snabbchat +Enter a new PSP nickname = Mata in ett nytt PSP-nickname Enter Quick Chat 1 = Snabbchat 1 Enter Quick Chat 2 = Snabbchat 2 Enter Quick Chat 3 = Snabbchat 3 Enter Quick Chat 4 = Snabbchat 4 Enter Quick Chat 5 = Snabbchat 5 Error = Error -Failed to Bind Localhost IP = Failed to bind localhost IP -Failed to Bind Port = Failed to bind port -Failed to connect to Adhoc Server = Failed to connect to ad hoc server +Failed to Bind Localhost IP = Misslyckades binda localhost-IP +Failed to Bind Port = Misslyckades binda port +Failed to connect to Adhoc Server = Misslyckades koppla upp till adhoc-servern Forced First Connect = Forced first connect (faster connect) -GM: Data from Unknown Port = GM: Data from Unknown Port +GM: Data from Unknown Port = GM: Data från okänd port Hostname = Hostname Invalid IP or hostname = Ogiltigt IP eller hostname -Minimum Timeout = Minimum timeout (override in ms, 0 = default) -Misc = Miscellaneous (default = PSP compatibility) +Minimum Timeout = Minimal timeout (override in ms, 0 = default) +Misc = Miscellaneous (default = PSP-kompatibilitet) Network Initialized = Nätverk initierat -Please change your Port Offset = Please change your port offset -Port offset = Port offset(0 = PSP compatibility) -Open PPSSPP Multiplayer Wiki Page = Open PPSSPP Multiplayer Wiki Page -proAdhocServer Address: = Ad hoc server address: +Please change your Port Offset = Vänligen ändra din port-offset +Port offset = Port offset (0 = PSP-kompatibilitet) +Open PPSSPP Multiplayer Wiki Page = Öppna PPSSPP Multiplayer Wiki Page +proAdhocServer Address: = Adhoc server-address: Quick Chat 1 = Snabbchat 1 Quick Chat 2 = Snabbchat 2 Quick Chat 3 = Snabbchat 3 @@ -879,7 +879,7 @@ RedBlue = Röd/Blå glasögon Saturation = Färgmättnad Scanlines = Scan-linjer Sharpen = Skärpa -SideBySide = Side by side (SBS) +SideBySide = Sida vid sida (SBS) SSAA(Gauss) = Supersampling AA (Gauss) Strength = Styrka Tex4xBRZ = 4xBRZ @@ -1079,12 +1079,12 @@ State load undone = State load ogjort Untitled PSP game = Untitled PSP game [Search] -Clear filter = Clear filter +Clear filter = Rensa filter Filter = Filter -Filtering settings by '%1' = Filtering settings by '%1' -Find settings = Find settings -No settings matched '%1' = No settings matched '%1' -Search term = Search term +Filtering settings by '%1' = Filtrerar inställningar efter '%1' +Find settings = Sök inställningar +No settings matched '%1' = Inga inställningar matchade '%1' +Search term = Sökterm [Store] Already Installed = Redan installerad @@ -1318,7 +1318,7 @@ Game camera uses rotation smoothing = Game camera uses rotation smoothing Heads-up display detection = Heads-up display detection Heads-up display scale = Heads-up display scale Manual switching between flat screen and VR using SCREEN key = Manual switching between flat screen and VR using SCREEN key -Map controller movements to keys = Map controller movements to keys +Map controller movements to keys = Mappa kontroller-rörelser till knappar Map HMD rotations on keys instead of VR camera = Map HMD rotations on keys instead of VR camera Motion needed to generate action = Motion needed to generate action Stereoscopic vision (Experimental) = Stereoscopic vision (Experimental) From cf97038a2dbf77a451eccf0e14356362d8f8b779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 31 Jul 2023 23:32:24 +0200 Subject: [PATCH 167/167] Switch the naett submodule back to the upstream repo All my PRs are in now. --- .gitmodules | 2 +- ext/naett | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index af29087557..2dd1e55e80 100644 --- a/.gitmodules +++ b/.gitmodules @@ -49,4 +49,4 @@ url = https://github.com/RetroAchievements/rcheevos.git [submodule "ext/naett"] path = ext/naett - url = https://github.com/hrydgard/naett.git + url = https://github.com/erkkah/naett.git diff --git a/ext/naett b/ext/naett index 978500f0bc..8c3a5d9278 160000 --- a/ext/naett +++ b/ext/naett @@ -1 +1 @@ -Subproject commit 978500f0bc2d3a5e2a235e27826af6166016fea7 +Subproject commit 8c3a5d92780eacacc24ab34a12cf585997e08ff9