diff --git a/CMakeLists.txt b/CMakeLists.txt index d0c27c503..dac3b19ab 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() -project(shadPS4) +project(shadPS4 CXX C ASM) # Forcing PIE makes sure that the base address is high enough so that it doesn't clash with the PS4 memory. if(UNIX AND NOT APPLE) @@ -392,7 +392,8 @@ set(USBD_LIB src/core/libraries/usbd/usbd.cpp src/core/libraries/usbd/usbd.h ) -set(FIBER_LIB src/core/libraries/fiber/fiber.cpp +set(FIBER_LIB src/core/libraries/fiber/fiber_context.s + src/core/libraries/fiber/fiber.cpp src/core/libraries/fiber/fiber.h src/core/libraries/fiber/fiber_error.h ) @@ -630,6 +631,8 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h src/shader_recompiler/backend/spirv/emit_spirv_instructions.h src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp src/shader_recompiler/backend/spirv/emit_spirv_logical.cpp + src/shader_recompiler/backend/spirv/emit_spirv_quad_rect.cpp + src/shader_recompiler/backend/spirv/emit_spirv_quad_rect.h src/shader_recompiler/backend/spirv/emit_spirv_select.cpp src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp src/shader_recompiler/backend/spirv/emit_spirv_special.cpp diff --git a/README.md b/README.md index 7ef5bdf65..7ba13ad47 100644 --- a/README.md +++ b/README.md @@ -86,32 +86,32 @@ F12 | Trigger RenderDoc Capture > [!NOTE] > Xbox and DualShock controllers work out of the box. -| Controller button | Keyboard equivelant | -|-------------|-------------| -LEFT AXIS UP | W | -LEFT AXIS DOWN | S | -LEFT AXIS LEFT | A | -LEFT AXIS RIGHT | D | -RIGHT AXIS UP | I | -RIGHT AXIS DOWN | K | -RIGHT AXIS LEFT | J | -RIGHT AXIS RIGHT | L | -TRIANGLE | Numpad 8 | -CIRCLE | Numpad 6 | -CROSS | Numpad 2 | -SQUARE | Numpad 4 | -PAD UP | UP | -PAD DOWN | DOWN | -PAD LEFT | LEFT | -PAD RIGHT | RIGHT | -OPTIONS | RETURN | -BACK BUTTON / TOUCH PAD | SPACE | -L1 | Q | -R1 | U | -L2 | E | -R2 | O | -L3 | X | -R3 | M | +| Controller button | Keyboard equivelant | Mac alternative | +|-------------|-------------|--------------| +LEFT AXIS UP | W | | +LEFT AXIS DOWN | S | | +LEFT AXIS LEFT | A | | +LEFT AXIS RIGHT | D | | +RIGHT AXIS UP | I | | +RIGHT AXIS DOWN | K | | +RIGHT AXIS LEFT | J | | +RIGHT AXIS RIGHT | L | | +TRIANGLE | Numpad 8 | C | +CIRCLE | Numpad 6 | B | +CROSS | Numpad 2 | N | +SQUARE | Numpad 4 | V | +PAD UP | UP | | +PAD DOWN | DOWN | | +PAD LEFT | LEFT | | +PAD RIGHT | RIGHT | | +OPTIONS | RETURN | | +BACK BUTTON / TOUCH PAD | SPACE | | +L1 | Q | | +R1 | U | | +L2 | E | | +R2 | O | | +L3 | X | | +R3 | M | | # Main team diff --git a/documents/building-windows.md b/documents/building-windows.md index d01e7b81e..845cdd10f 100644 --- a/documents/building-windows.md +++ b/documents/building-windows.md @@ -25,7 +25,7 @@ Once you are within the installer: Beware, this requires you to create a Qt account. If you do not want to do this, please follow the MSYS2/MinGW compilation method instead. -1. Under the current, non beta version of Qt (at the time of writing 6.7.3), select the option `MSVC 2022 64-bit` or similar. +1. Under the current, non beta version of Qt (at the time of writing 6.7.3), select the option `MSVC 2022 64-bit` or similar, as well as `QT Multimedia`. If you are on Windows on ARM / Qualcomm Snapdragon Elite X, select `MSVC 2022 ARM64` instead. Go through the installation normally. If you know what you are doing, you may unselect individual components that eat up too much disk space. @@ -64,7 +64,7 @@ Go through the Git for Windows installation as normal Your shadps4.exe will be in `C:\path\to\source\Build\x64-Clang-Release\` To automatically populate the necessary files to run shadPS4.exe, run in a command prompt or terminal: -`C:\Qt\6.7.3\msvc2022_64\bin\windeployqt.exe "C:\path\to\shadps4.exe"` +`C:\Qt\6.7.3\msvc2022_64\bin\windeployqt6.exe "C:\path\to\shadps4.exe"` (Change Qt path if you've installed it to non-default path) ## Option 2: MSYS2/MinGW diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index dbe6794d8..4350948b7 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -189,7 +189,11 @@ add_library(Dear_ImGui target_include_directories(Dear_ImGui INTERFACE dear_imgui/) # Tracy -option(TRACY_ENABLE "" ON) +if (CMAKE_BUILD_TYPE STREQUAL "Release") + option(TRACY_ENABLE "" OFF) +else() + option(TRACY_ENABLE "" ON) +endif() option(TRACY_NO_CRASH_HANDLER "" ON) # Otherwise texture cache exceptions will be treaten as a crash option(TRACY_ON_DEMAND "" ON) option(TRACY_NO_FRAME_IMAGE "" ON) diff --git a/src/common/debug.h b/src/common/debug.h index 882e9e5c4..92bf72550 100644 --- a/src/common/debug.h +++ b/src/common/debug.h @@ -14,7 +14,11 @@ #include static inline bool IsProfilerConnected() { +#if TRACY_ENABLE return tracy::GetProfiler().IsConnected(); +#else + return false; +#endif } #define TRACY_GPU_ENABLED 0 diff --git a/src/common/div_ceil.h b/src/common/div_ceil.h index de275e76f..c12477d42 100755 --- a/src/common/div_ceil.h +++ b/src/common/div_ceil.h @@ -1,25 +1,25 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -namespace Common { - -/// Ceiled integer division. -template - requires std::is_integral_v && std::is_unsigned_v -[[nodiscard]] constexpr N DivCeil(N number, D divisor) { - return static_cast((static_cast(number) + divisor - 1) / divisor); -} - -/// Ceiled integer division with logarithmic divisor in base 2 -template - requires std::is_integral_v && std::is_unsigned_v -[[nodiscard]] constexpr N DivCeilLog2(N value, D alignment_log2) { - return static_cast((static_cast(value) + (D(1) << alignment_log2) - 1) >> alignment_log2); -} - -} // namespace Common +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace Common { + +/// Ceiled integer division. +template + requires std::is_integral_v && std::is_unsigned_v +[[nodiscard]] constexpr N DivCeil(N number, D divisor) { + return static_cast((static_cast(number) + divisor - 1) / divisor); +} + +/// Ceiled integer division with logarithmic divisor in base 2 +template + requires std::is_integral_v && std::is_unsigned_v +[[nodiscard]] constexpr N DivCeilLog2(N value, D alignment_log2) { + return static_cast((static_cast(value) + (D(1) << alignment_log2) - 1) >> alignment_log2); +} + +} // namespace Common diff --git a/src/common/io_file.h b/src/common/io_file.h index feb2110ac..45787a092 100644 --- a/src/common/io_file.h +++ b/src/common/io_file.h @@ -207,7 +207,7 @@ public: return WriteSpan(string); } - static size_t WriteBytes(const std::filesystem::path path, std::span data) { + static size_t WriteBytes(const std::filesystem::path path, const auto& data) { IOFile out(path, FileAccessMode::Write); return out.Write(data); } diff --git a/src/common/ntapi.cpp b/src/common/ntapi.cpp index c76c4657e..46ec57e0a 100644 --- a/src/common/ntapi.cpp +++ b/src/common/ntapi.cpp @@ -1,30 +1,30 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#ifdef _WIN32 - -#include "ntapi.h" - -NtClose_t NtClose = nullptr; -NtSetInformationFile_t NtSetInformationFile = nullptr; -NtCreateThread_t NtCreateThread = nullptr; -NtTerminateThread_t NtTerminateThread = nullptr; -NtQueueApcThreadEx_t NtQueueApcThreadEx = nullptr; - -namespace Common::NtApi { - -void Initialize() { - HMODULE nt_handle = GetModuleHandleA("ntdll.dll"); - - // http://stackoverflow.com/a/31411628/4725495 - NtClose = (NtClose_t)GetProcAddress(nt_handle, "NtClose"); - NtSetInformationFile = - (NtSetInformationFile_t)GetProcAddress(nt_handle, "NtSetInformationFile"); - NtCreateThread = (NtCreateThread_t)GetProcAddress(nt_handle, "NtCreateThread"); - NtTerminateThread = (NtTerminateThread_t)GetProcAddress(nt_handle, "NtTerminateThread"); - NtQueueApcThreadEx = (NtQueueApcThreadEx_t)GetProcAddress(nt_handle, "NtQueueApcThreadEx"); -} - -} // namespace Common::NtApi - -#endif +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef _WIN32 + +#include "ntapi.h" + +NtClose_t NtClose = nullptr; +NtSetInformationFile_t NtSetInformationFile = nullptr; +NtCreateThread_t NtCreateThread = nullptr; +NtTerminateThread_t NtTerminateThread = nullptr; +NtQueueApcThreadEx_t NtQueueApcThreadEx = nullptr; + +namespace Common::NtApi { + +void Initialize() { + HMODULE nt_handle = GetModuleHandleA("ntdll.dll"); + + // http://stackoverflow.com/a/31411628/4725495 + NtClose = (NtClose_t)GetProcAddress(nt_handle, "NtClose"); + NtSetInformationFile = + (NtSetInformationFile_t)GetProcAddress(nt_handle, "NtSetInformationFile"); + NtCreateThread = (NtCreateThread_t)GetProcAddress(nt_handle, "NtCreateThread"); + NtTerminateThread = (NtTerminateThread_t)GetProcAddress(nt_handle, "NtTerminateThread"); + NtQueueApcThreadEx = (NtQueueApcThreadEx_t)GetProcAddress(nt_handle, "NtQueueApcThreadEx"); +} + +} // namespace Common::NtApi + +#endif diff --git a/src/common/ntapi.h b/src/common/ntapi.h index daab8440d..c876af58f 100644 --- a/src/common/ntapi.h +++ b/src/common/ntapi.h @@ -1,554 +1,554 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#ifdef _WIN32 - -#include -#include "common/types.h" - -typedef enum _FILE_INFORMATION_CLASS { - FileDirectoryInformation = 1, - FileFullDirectoryInformation = 2, - FileBothDirectoryInformation = 3, - FileBasicInformation = 4, - FileStandardInformation = 5, - FileInternalInformation = 6, - FileEaInformation = 7, - FileAccessInformation = 8, - FileNameInformation = 9, - FileRenameInformation = 10, - FileLinkInformation = 11, - FileNamesInformation = 12, - FileDispositionInformation = 13, - FilePositionInformation = 14, - FileFullEaInformation = 15, - FileModeInformation = 16, - FileAlignmentInformation = 17, - FileAllInformation = 18, - FileAllocationInformation = 19, - FileEndOfFileInformation = 20, - FileAlternateNameInformation = 21, - FileStreamInformation = 22, - FilePipeInformation = 23, - FilePipeLocalInformation = 24, - FilePipeRemoteInformation = 25, - FileMailslotQueryInformation = 26, - FileMailslotSetInformation = 27, - FileCompressionInformation = 28, - FileObjectIdInformation = 29, - FileCompletionInformation = 30, - FileMoveClusterInformation = 31, - FileQuotaInformation = 32, - FileReparsePointInformation = 33, - FileNetworkOpenInformation = 34, - FileAttributeTagInformation = 35, - FileTrackingInformation = 36, - FileIdBothDirectoryInformation = 37, - FileIdFullDirectoryInformation = 38, - FileValidDataLengthInformation = 39, - FileShortNameInformation = 40, - FileIoCompletionNotificationInformation = 41, - FileIoStatusBlockRangeInformation = 42, - FileIoPriorityHintInformation = 43, - FileSfioReserveInformation = 44, - FileSfioVolumeInformation = 45, - FileHardLinkInformation = 46, - FileProcessIdsUsingFileInformation = 47, - FileNormalizedNameInformation = 48, - FileNetworkPhysicalNameInformation = 49, - FileIdGlobalTxDirectoryInformation = 50, - FileIsRemoteDeviceInformation = 51, - FileUnusedInformation = 52, - FileNumaNodeInformation = 53, - FileStandardLinkInformation = 54, - FileRemoteProtocolInformation = 55, - FileRenameInformationBypassAccessCheck = 56, - FileLinkInformationBypassAccessCheck = 57, - FileVolumeNameInformation = 58, - FileIdInformation = 59, - FileIdExtdDirectoryInformation = 60, - FileReplaceCompletionInformation = 61, - FileHardLinkFullIdInformation = 62, - FileIdExtdBothDirectoryInformation = 63, - FileDispositionInformationEx = 64, - FileRenameInformationEx = 65, - FileRenameInformationExBypassAccessCheck = 66, - FileDesiredStorageClassInformation = 67, - FileStatInformation = 68, - FileMemoryPartitionInformation = 69, - FileStatLxInformation = 70, - FileCaseSensitiveInformation = 71, - FileLinkInformationEx = 72, - FileLinkInformationExBypassAccessCheck = 73, - FileStorageReserveIdInformation = 74, - FileCaseSensitiveInformationForceAccessCheck = 75, - FileKnownFolderInformation = 76, - FileStatBasicInformation = 77, - FileId64ExtdDirectoryInformation = 78, - FileId64ExtdBothDirectoryInformation = 79, - FileIdAllExtdDirectoryInformation = 80, - FileIdAllExtdBothDirectoryInformation = 81, - FileStreamReservationInformation, - FileMupProviderInfo, - FileMaximumInformation -} FILE_INFORMATION_CLASS, - *PFILE_INFORMATION_CLASS; - -typedef struct _IO_STATUS_BLOCK { - union { - u32 Status; - PVOID Pointer; - }; - ULONG_PTR Information; -} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; - -typedef struct _FILE_DISPOSITION_INFORMATION { - BOOLEAN DeleteFile; -} FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION; - -typedef struct _UNICODE_STRING { - USHORT Length; - USHORT MaximumLength; - PWCH Buffer; -} UNICODE_STRING, *PUNICODE_STRING; - -typedef const UNICODE_STRING* PCUNICODE_STRING; - -typedef struct _OBJECT_ATTRIBUTES { - ULONG Length; - HANDLE RootDirectory; - PCUNICODE_STRING ObjectName; - ULONG Attributes; - PVOID SecurityDescriptor; // PSECURITY_DESCRIPTOR; - PVOID SecurityQualityOfService; // PSECURITY_QUALITY_OF_SERVICE -} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; - -typedef const OBJECT_ATTRIBUTES* PCOBJECT_ATTRIBUTES; - -typedef struct _CLIENT_ID { - HANDLE UniqueProcess; - HANDLE UniqueThread; -} CLIENT_ID, *PCLIENT_ID; - -typedef struct _INITIAL_TEB { - struct { - PVOID OldStackBase; - PVOID OldStackLimit; - } OldInitialTeb; - PVOID StackBase; - PVOID StackLimit; - PVOID StackAllocationBase; -} INITIAL_TEB, *PINITIAL_TEB; - -typedef struct _PEB_LDR_DATA { - ULONG Length; - BOOLEAN Initialized; - PVOID SsHandle; - LIST_ENTRY InLoadOrderModuleList; - LIST_ENTRY InMemoryOrderModuleList; - LIST_ENTRY InInitializationOrderModuleList; - PVOID EntryInProgress; - BOOLEAN ShutdownInProgress; - HANDLE ShutdownThreadId; -} PEB_LDR_DATA, *PPEB_LDR_DATA; - -typedef struct _CURDIR { - UNICODE_STRING DosPath; - PVOID Handle; -} CURDIR, *PCURDIR; - -typedef struct RTL_DRIVE_LETTER_CURDIR { - USHORT Flags; - USHORT Length; - ULONG TimeStamp; - UNICODE_STRING DosPath; -} RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR; - -typedef struct _RTL_USER_PROCESS_PARAMETERS { - ULONG AllocationSize; - ULONG Size; - ULONG Flags; - ULONG DebugFlags; - HANDLE ConsoleHandle; - ULONG ConsoleFlags; - HANDLE hStdInput; - HANDLE hStdOutput; - HANDLE hStdError; - CURDIR CurrentDirectory; - UNICODE_STRING DllPath; - UNICODE_STRING ImagePathName; - UNICODE_STRING CommandLine; - PWSTR Environment; - ULONG dwX; - ULONG dwY; - ULONG dwXSize; - ULONG dwYSize; - ULONG dwXCountChars; - ULONG dwYCountChars; - ULONG dwFillAttribute; - ULONG dwFlags; - ULONG wShowWindow; - UNICODE_STRING WindowTitle; - UNICODE_STRING Desktop; - UNICODE_STRING ShellInfo; - UNICODE_STRING RuntimeInfo; - RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[0x20]; - ULONG_PTR EnvironmentSize; - ULONG_PTR EnvironmentVersion; - PVOID PackageDependencyData; - ULONG ProcessGroupId; - ULONG LoaderThreads; -} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; - -typedef struct tagRTL_BITMAP { - ULONG SizeOfBitMap; - PULONG Buffer; -} RTL_BITMAP, *PRTL_BITMAP; - -typedef struct { - UINT next; - UINT id; - ULONGLONG addr; - ULONGLONG size; - UINT args[4]; -} CROSS_PROCESS_WORK_ENTRY; - -typedef union { - struct { - UINT first; - UINT counter; - }; - volatile LONGLONG hdr; -} CROSS_PROCESS_WORK_HDR; - -typedef struct { - CROSS_PROCESS_WORK_HDR free_list; - CROSS_PROCESS_WORK_HDR work_list; - ULONGLONG unknown[4]; - CROSS_PROCESS_WORK_ENTRY entries[1]; -} CROSS_PROCESS_WORK_LIST; - -typedef struct _CHPEV2_PROCESS_INFO { - ULONG Wow64ExecuteFlags; /* 000 */ - USHORT NativeMachineType; /* 004 */ - USHORT EmulatedMachineType; /* 006 */ - HANDLE SectionHandle; /* 008 */ - CROSS_PROCESS_WORK_LIST* CrossProcessWorkList; /* 010 */ - void* unknown; /* 018 */ -} CHPEV2_PROCESS_INFO, *PCHPEV2_PROCESS_INFO; - -typedef u64(__stdcall* KERNEL_CALLBACK_PROC)(void*, ULONG); - -typedef struct _PEB { /* win32/win64 */ - BOOLEAN InheritedAddressSpace; /* 000/000 */ - BOOLEAN ReadImageFileExecOptions; /* 001/001 */ - BOOLEAN BeingDebugged; /* 002/002 */ - UCHAR ImageUsedLargePages : 1; /* 003/003 */ - UCHAR IsProtectedProcess : 1; - UCHAR IsImageDynamicallyRelocated : 1; - UCHAR SkipPatchingUser32Forwarders : 1; - UCHAR IsPackagedProcess : 1; - UCHAR IsAppContainer : 1; - UCHAR IsProtectedProcessLight : 1; - UCHAR IsLongPathAwareProcess : 1; - HANDLE Mutant; /* 004/008 */ - HMODULE ImageBaseAddress; /* 008/010 */ - PPEB_LDR_DATA LdrData; /* 00c/018 */ - RTL_USER_PROCESS_PARAMETERS* ProcessParameters; /* 010/020 */ - PVOID SubSystemData; /* 014/028 */ - HANDLE ProcessHeap; /* 018/030 */ - PRTL_CRITICAL_SECTION FastPebLock; /* 01c/038 */ - PVOID AtlThunkSListPtr; /* 020/040 */ - PVOID IFEOKey; /* 024/048 */ - ULONG ProcessInJob : 1; /* 028/050 */ - ULONG ProcessInitializing : 1; - ULONG ProcessUsingVEH : 1; - ULONG ProcessUsingVCH : 1; - ULONG ProcessUsingFTH : 1; - ULONG ProcessPreviouslyThrottled : 1; - ULONG ProcessCurrentlyThrottled : 1; - ULONG ProcessImagesHotPatched : 1; - ULONG ReservedBits0 : 24; - KERNEL_CALLBACK_PROC* KernelCallbackTable; /* 02c/058 */ - ULONG Reserved; /* 030/060 */ - ULONG AtlThunkSListPtr32; /* 034/064 */ - PVOID ApiSetMap; /* 038/068 */ - ULONG TlsExpansionCounter; /* 03c/070 */ - PRTL_BITMAP TlsBitmap; /* 040/078 */ - ULONG TlsBitmapBits[2]; /* 044/080 */ - PVOID ReadOnlySharedMemoryBase; /* 04c/088 */ - PVOID SharedData; /* 050/090 */ - PVOID* ReadOnlyStaticServerData; /* 054/098 */ - PVOID AnsiCodePageData; /* 058/0a0 */ - PVOID OemCodePageData; /* 05c/0a8 */ - PVOID UnicodeCaseTableData; /* 060/0b0 */ - ULONG NumberOfProcessors; /* 064/0b8 */ - ULONG NtGlobalFlag; /* 068/0bc */ - LARGE_INTEGER CriticalSectionTimeout; /* 070/0c0 */ - SIZE_T HeapSegmentReserve; /* 078/0c8 */ - SIZE_T HeapSegmentCommit; /* 07c/0d0 */ - SIZE_T HeapDeCommitTotalFreeThreshold; /* 080/0d8 */ - SIZE_T HeapDeCommitFreeBlockThreshold; /* 084/0e0 */ - ULONG NumberOfHeaps; /* 088/0e8 */ - ULONG MaximumNumberOfHeaps; /* 08c/0ec */ - PVOID* ProcessHeaps; /* 090/0f0 */ - PVOID GdiSharedHandleTable; /* 094/0f8 */ - PVOID ProcessStarterHelper; /* 098/100 */ - PVOID GdiDCAttributeList; /* 09c/108 */ - PVOID LoaderLock; /* 0a0/110 */ - ULONG OSMajorVersion; /* 0a4/118 */ - ULONG OSMinorVersion; /* 0a8/11c */ - ULONG OSBuildNumber; /* 0ac/120 */ - ULONG OSPlatformId; /* 0b0/124 */ - ULONG ImageSubSystem; /* 0b4/128 */ - ULONG ImageSubSystemMajorVersion; /* 0b8/12c */ - ULONG ImageSubSystemMinorVersion; /* 0bc/130 */ - KAFFINITY ActiveProcessAffinityMask; /* 0c0/138 */ -#ifdef _WIN64 - ULONG GdiHandleBuffer[60]; /* /140 */ -#else - ULONG GdiHandleBuffer[34]; /* 0c4/ */ -#endif - PVOID PostProcessInitRoutine; /* 14c/230 */ - PRTL_BITMAP TlsExpansionBitmap; /* 150/238 */ - ULONG TlsExpansionBitmapBits[32]; /* 154/240 */ - ULONG SessionId; /* 1d4/2c0 */ - ULARGE_INTEGER AppCompatFlags; /* 1d8/2c8 */ - ULARGE_INTEGER AppCompatFlagsUser; /* 1e0/2d0 */ - PVOID ShimData; /* 1e8/2d8 */ - PVOID AppCompatInfo; /* 1ec/2e0 */ - UNICODE_STRING CSDVersion; /* 1f0/2e8 */ - PVOID ActivationContextData; /* 1f8/2f8 */ - PVOID ProcessAssemblyStorageMap; /* 1fc/300 */ - PVOID SystemDefaultActivationData; /* 200/308 */ - PVOID SystemAssemblyStorageMap; /* 204/310 */ - SIZE_T MinimumStackCommit; /* 208/318 */ - PVOID* FlsCallback; /* 20c/320 */ - LIST_ENTRY FlsListHead; /* 210/328 */ - union { - PRTL_BITMAP FlsBitmap; /* 218/338 */ -#ifdef _WIN64 - CHPEV2_PROCESS_INFO* ChpeV2ProcessInfo; /* /338 */ -#endif - }; - ULONG FlsBitmapBits[4]; /* 21c/340 */ - ULONG FlsHighIndex; /* 22c/350 */ - PVOID WerRegistrationData; /* 230/358 */ - PVOID WerShipAssertPtr; /* 234/360 */ - PVOID EcCodeBitMap; /* 238/368 */ - PVOID pImageHeaderHash; /* 23c/370 */ - ULONG HeapTracingEnabled : 1; /* 240/378 */ - ULONG CritSecTracingEnabled : 1; - ULONG LibLoaderTracingEnabled : 1; - ULONG SpareTracingBits : 29; - ULONGLONG CsrServerReadOnlySharedMemoryBase; /* 248/380 */ - ULONG TppWorkerpListLock; /* 250/388 */ - LIST_ENTRY TppWorkerpList; /* 254/390 */ - PVOID WaitOnAddressHashTable[0x80]; /* 25c/3a0 */ - PVOID TelemetryCoverageHeader; /* 45c/7a0 */ - ULONG CloudFileFlags; /* 460/7a8 */ - ULONG CloudFileDiagFlags; /* 464/7ac */ - CHAR PlaceholderCompatibilityMode; /* 468/7b0 */ - CHAR PlaceholderCompatibilityModeReserved[7]; /* 469/7b1 */ - PVOID LeapSecondData; /* 470/7b8 */ - ULONG LeapSecondFlags; /* 474/7c0 */ - ULONG NtGlobalFlag2; /* 478/7c4 */ -} PEB, *PPEB; - -typedef struct _RTL_ACTIVATION_CONTEXT_STACK_FRAME { - struct _RTL_ACTIVATION_CONTEXT_STACK_FRAME* Previous; - struct _ACTIVATION_CONTEXT* ActivationContext; - ULONG Flags; -} RTL_ACTIVATION_CONTEXT_STACK_FRAME, *PRTL_ACTIVATION_CONTEXT_STACK_FRAME; - -typedef struct _ACTIVATION_CONTEXT_STACK { - RTL_ACTIVATION_CONTEXT_STACK_FRAME* ActiveFrame; - LIST_ENTRY FrameListCache; - ULONG Flags; - ULONG NextCookieSequenceNumber; - ULONG_PTR StackId; -} ACTIVATION_CONTEXT_STACK, *PACTIVATION_CONTEXT_STACK; - -typedef struct _GDI_TEB_BATCH { - ULONG Offset; - HANDLE HDC; - ULONG Buffer[0x136]; -} GDI_TEB_BATCH; - -typedef struct _TEB_ACTIVE_FRAME_CONTEXT { - ULONG Flags; - const char* FrameName; -} TEB_ACTIVE_FRAME_CONTEXT, *PTEB_ACTIVE_FRAME_CONTEXT; - -typedef struct _TEB_ACTIVE_FRAME { - ULONG Flags; - struct _TEB_ACTIVE_FRAME* Previous; - TEB_ACTIVE_FRAME_CONTEXT* Context; -} TEB_ACTIVE_FRAME, *PTEB_ACTIVE_FRAME; - -typedef struct _TEB { /* win32/win64 */ - NT_TIB Tib; /* 000/0000 */ - PVOID EnvironmentPointer; /* 01c/0038 */ - CLIENT_ID ClientId; /* 020/0040 */ - PVOID ActiveRpcHandle; /* 028/0050 */ - PVOID ThreadLocalStoragePointer; /* 02c/0058 */ - PPEB Peb; /* 030/0060 */ - ULONG LastErrorValue; /* 034/0068 */ - ULONG CountOfOwnedCriticalSections; /* 038/006c */ - PVOID CsrClientThread; /* 03c/0070 */ - PVOID Win32ThreadInfo; /* 040/0078 */ - ULONG User32Reserved[26]; /* 044/0080 */ - ULONG UserReserved[5]; /* 0ac/00e8 */ - PVOID WOW32Reserved; /* 0c0/0100 */ - ULONG CurrentLocale; /* 0c4/0108 */ - ULONG FpSoftwareStatusRegister; /* 0c8/010c */ - PVOID ReservedForDebuggerInstrumentation[16]; /* 0cc/0110 */ -#ifdef _WIN64 - PVOID SystemReserved1[30]; /* /0190 */ -#else - PVOID SystemReserved1[26]; /* 10c/ */ -#endif - char PlaceholderCompatibilityMode; /* 174/0280 */ - BOOLEAN PlaceholderHydrationAlwaysExplicit; /* 175/0281 */ - char PlaceholderReserved[10]; /* 176/0282 */ - DWORD ProxiedProcessId; /* 180/028c */ - ACTIVATION_CONTEXT_STACK ActivationContextStack; /* 184/0290 */ - UCHAR WorkingOnBehalfOfTicket[8]; /* 19c/02b8 */ - LONG ExceptionCode; /* 1a4/02c0 */ - ACTIVATION_CONTEXT_STACK* ActivationContextStackPointer; /* 1a8/02c8 */ - ULONG_PTR InstrumentationCallbackSp; /* 1ac/02d0 */ - ULONG_PTR InstrumentationCallbackPreviousPc; /* 1b0/02d8 */ - ULONG_PTR InstrumentationCallbackPreviousSp; /* 1b4/02e0 */ -#ifdef _WIN64 - ULONG TxFsContext; /* /02e8 */ - BOOLEAN InstrumentationCallbackDisabled; /* /02ec */ - BOOLEAN UnalignedLoadStoreExceptions; /* /02ed */ -#else - BOOLEAN InstrumentationCallbackDisabled; /* 1b8/ */ - BYTE SpareBytes1[23]; /* 1b9/ */ - ULONG TxFsContext; /* 1d0/ */ -#endif - GDI_TEB_BATCH GdiTebBatch; /* 1d4/02f0 */ - CLIENT_ID RealClientId; /* 6b4/07d8 */ - HANDLE GdiCachedProcessHandle; /* 6bc/07e8 */ - ULONG GdiClientPID; /* 6c0/07f0 */ - ULONG GdiClientTID; /* 6c4/07f4 */ - PVOID GdiThreadLocaleInfo; /* 6c8/07f8 */ - ULONG_PTR Win32ClientInfo[62]; /* 6cc/0800 */ - PVOID glDispatchTable[233]; /* 7c4/09f0 */ - PVOID glReserved1[29]; /* b68/1138 */ - PVOID glReserved2; /* bdc/1220 */ - PVOID glSectionInfo; /* be0/1228 */ - PVOID glSection; /* be4/1230 */ - PVOID glTable; /* be8/1238 */ - PVOID glCurrentRC; /* bec/1240 */ - PVOID glContext; /* bf0/1248 */ - ULONG LastStatusValue; /* bf4/1250 */ - UNICODE_STRING StaticUnicodeString; /* bf8/1258 */ - WCHAR StaticUnicodeBuffer[261]; /* c00/1268 */ - PVOID DeallocationStack; /* e0c/1478 */ - PVOID TlsSlots[64]; /* e10/1480 */ - LIST_ENTRY TlsLinks; /* f10/1680 */ - PVOID Vdm; /* f18/1690 */ - PVOID ReservedForNtRpc; /* f1c/1698 */ - PVOID DbgSsReserved[2]; /* f20/16a0 */ - ULONG HardErrorMode; /* f28/16b0 */ -#ifdef _WIN64 - PVOID Instrumentation[11]; /* /16b8 */ -#else - PVOID Instrumentation[9]; /* f2c/ */ -#endif - GUID ActivityId; /* f50/1710 */ - PVOID SubProcessTag; /* f60/1720 */ - PVOID PerflibData; /* f64/1728 */ - PVOID EtwTraceData; /* f68/1730 */ - PVOID WinSockData; /* f6c/1738 */ - ULONG GdiBatchCount; /* f70/1740 */ - ULONG IdealProcessorValue; /* f74/1744 */ - ULONG GuaranteedStackBytes; /* f78/1748 */ - PVOID ReservedForPerf; /* f7c/1750 */ - PVOID ReservedForOle; /* f80/1758 */ - ULONG WaitingOnLoaderLock; /* f84/1760 */ - PVOID SavedPriorityState; /* f88/1768 */ - ULONG_PTR ReservedForCodeCoverage; /* f8c/1770 */ - PVOID ThreadPoolData; /* f90/1778 */ - PVOID* TlsExpansionSlots; /* f94/1780 */ -#ifdef _WIN64 - union { - PVOID DeallocationBStore; /* /1788 */ - PVOID* ChpeV2CpuAreaInfo; /* /1788 */ - } DUMMYUNIONNAME; - PVOID BStoreLimit; /* /1790 */ -#endif - ULONG MuiGeneration; /* f98/1798 */ - ULONG IsImpersonating; /* f9c/179c */ - PVOID NlsCache; /* fa0/17a0 */ - PVOID ShimData; /* fa4/17a8 */ - ULONG HeapVirtualAffinity; /* fa8/17b0 */ - PVOID CurrentTransactionHandle; /* fac/17b8 */ - TEB_ACTIVE_FRAME* ActiveFrame; /* fb0/17c0 */ - PVOID* FlsSlots; /* fb4/17c8 */ - PVOID PreferredLanguages; /* fb8/17d0 */ - PVOID UserPrefLanguages; /* fbc/17d8 */ - PVOID MergedPrefLanguages; /* fc0/17e0 */ - ULONG MuiImpersonation; /* fc4/17e8 */ - USHORT CrossTebFlags; /* fc8/17ec */ - USHORT SameTebFlags; /* fca/17ee */ - PVOID TxnScopeEnterCallback; /* fcc/17f0 */ - PVOID TxnScopeExitCallback; /* fd0/17f8 */ - PVOID TxnScopeContext; /* fd4/1800 */ - ULONG LockCount; /* fd8/1808 */ - LONG WowTebOffset; /* fdc/180c */ - PVOID ResourceRetValue; /* fe0/1810 */ - PVOID ReservedForWdf; /* fe4/1818 */ - ULONGLONG ReservedForCrt; /* fe8/1820 */ - GUID EffectiveContainerId; /* ff0/1828 */ -} TEB, *PTEB; -static_assert(offsetof(TEB, DeallocationStack) == - 0x1478); /* The only member we care about at the moment */ - -typedef enum _QUEUE_USER_APC_FLAGS { - QueueUserApcFlagsNone, - QueueUserApcFlagsSpecialUserApc, - QueueUserApcFlagsMaxValue -} QUEUE_USER_APC_FLAGS; - -typedef union _USER_APC_OPTION { - ULONG_PTR UserApcFlags; - HANDLE MemoryReserveHandle; -} USER_APC_OPTION, *PUSER_APC_OPTION; - -using PPS_APC_ROUTINE = void (*)(PVOID ApcArgument1, PVOID ApcArgument2, PVOID ApcArgument3, - PCONTEXT Context); - -typedef u64(__stdcall* NtClose_t)(HANDLE Handle); - -typedef u64(__stdcall* NtSetInformationFile_t)(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, - PVOID FileInformation, ULONG Length, - FILE_INFORMATION_CLASS FileInformationClass); - -typedef u64(__stdcall* NtCreateThread_t)(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, - PCOBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle, - PCLIENT_ID ClientId, PCONTEXT ThreadContext, - PINITIAL_TEB InitialTeb, BOOLEAN CreateSuspended); - -typedef u64(__stdcall* NtTerminateThread_t)(HANDLE ThreadHandle, u64 ExitStatus); - -typedef u64(__stdcall* NtQueueApcThreadEx_t)(HANDLE ThreadHandle, - USER_APC_OPTION UserApcReserveHandle, - PPS_APC_ROUTINE ApcRoutine, PVOID ApcArgument1, - PVOID ApcArgument2, PVOID ApcArgument3); - -extern NtClose_t NtClose; -extern NtSetInformationFile_t NtSetInformationFile; -extern NtCreateThread_t NtCreateThread; -extern NtTerminateThread_t NtTerminateThread; -extern NtQueueApcThreadEx_t NtQueueApcThreadEx; - -namespace Common::NtApi { -void Initialize(); -} - -#endif +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef _WIN32 + +#include +#include "common/types.h" + +typedef enum _FILE_INFORMATION_CLASS { + FileDirectoryInformation = 1, + FileFullDirectoryInformation = 2, + FileBothDirectoryInformation = 3, + FileBasicInformation = 4, + FileStandardInformation = 5, + FileInternalInformation = 6, + FileEaInformation = 7, + FileAccessInformation = 8, + FileNameInformation = 9, + FileRenameInformation = 10, + FileLinkInformation = 11, + FileNamesInformation = 12, + FileDispositionInformation = 13, + FilePositionInformation = 14, + FileFullEaInformation = 15, + FileModeInformation = 16, + FileAlignmentInformation = 17, + FileAllInformation = 18, + FileAllocationInformation = 19, + FileEndOfFileInformation = 20, + FileAlternateNameInformation = 21, + FileStreamInformation = 22, + FilePipeInformation = 23, + FilePipeLocalInformation = 24, + FilePipeRemoteInformation = 25, + FileMailslotQueryInformation = 26, + FileMailslotSetInformation = 27, + FileCompressionInformation = 28, + FileObjectIdInformation = 29, + FileCompletionInformation = 30, + FileMoveClusterInformation = 31, + FileQuotaInformation = 32, + FileReparsePointInformation = 33, + FileNetworkOpenInformation = 34, + FileAttributeTagInformation = 35, + FileTrackingInformation = 36, + FileIdBothDirectoryInformation = 37, + FileIdFullDirectoryInformation = 38, + FileValidDataLengthInformation = 39, + FileShortNameInformation = 40, + FileIoCompletionNotificationInformation = 41, + FileIoStatusBlockRangeInformation = 42, + FileIoPriorityHintInformation = 43, + FileSfioReserveInformation = 44, + FileSfioVolumeInformation = 45, + FileHardLinkInformation = 46, + FileProcessIdsUsingFileInformation = 47, + FileNormalizedNameInformation = 48, + FileNetworkPhysicalNameInformation = 49, + FileIdGlobalTxDirectoryInformation = 50, + FileIsRemoteDeviceInformation = 51, + FileUnusedInformation = 52, + FileNumaNodeInformation = 53, + FileStandardLinkInformation = 54, + FileRemoteProtocolInformation = 55, + FileRenameInformationBypassAccessCheck = 56, + FileLinkInformationBypassAccessCheck = 57, + FileVolumeNameInformation = 58, + FileIdInformation = 59, + FileIdExtdDirectoryInformation = 60, + FileReplaceCompletionInformation = 61, + FileHardLinkFullIdInformation = 62, + FileIdExtdBothDirectoryInformation = 63, + FileDispositionInformationEx = 64, + FileRenameInformationEx = 65, + FileRenameInformationExBypassAccessCheck = 66, + FileDesiredStorageClassInformation = 67, + FileStatInformation = 68, + FileMemoryPartitionInformation = 69, + FileStatLxInformation = 70, + FileCaseSensitiveInformation = 71, + FileLinkInformationEx = 72, + FileLinkInformationExBypassAccessCheck = 73, + FileStorageReserveIdInformation = 74, + FileCaseSensitiveInformationForceAccessCheck = 75, + FileKnownFolderInformation = 76, + FileStatBasicInformation = 77, + FileId64ExtdDirectoryInformation = 78, + FileId64ExtdBothDirectoryInformation = 79, + FileIdAllExtdDirectoryInformation = 80, + FileIdAllExtdBothDirectoryInformation = 81, + FileStreamReservationInformation, + FileMupProviderInfo, + FileMaximumInformation +} FILE_INFORMATION_CLASS, + *PFILE_INFORMATION_CLASS; + +typedef struct _IO_STATUS_BLOCK { + union { + u32 Status; + PVOID Pointer; + }; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +typedef struct _FILE_DISPOSITION_INFORMATION { + BOOLEAN DeleteFile; +} FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION; + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWCH Buffer; +} UNICODE_STRING, *PUNICODE_STRING; + +typedef const UNICODE_STRING* PCUNICODE_STRING; + +typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PCUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; // PSECURITY_DESCRIPTOR; + PVOID SecurityQualityOfService; // PSECURITY_QUALITY_OF_SERVICE +} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; + +typedef const OBJECT_ATTRIBUTES* PCOBJECT_ATTRIBUTES; + +typedef struct _CLIENT_ID { + HANDLE UniqueProcess; + HANDLE UniqueThread; +} CLIENT_ID, *PCLIENT_ID; + +typedef struct _INITIAL_TEB { + struct { + PVOID OldStackBase; + PVOID OldStackLimit; + } OldInitialTeb; + PVOID StackBase; + PVOID StackLimit; + PVOID StackAllocationBase; +} INITIAL_TEB, *PINITIAL_TEB; + +typedef struct _PEB_LDR_DATA { + ULONG Length; + BOOLEAN Initialized; + PVOID SsHandle; + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + PVOID EntryInProgress; + BOOLEAN ShutdownInProgress; + HANDLE ShutdownThreadId; +} PEB_LDR_DATA, *PPEB_LDR_DATA; + +typedef struct _CURDIR { + UNICODE_STRING DosPath; + PVOID Handle; +} CURDIR, *PCURDIR; + +typedef struct RTL_DRIVE_LETTER_CURDIR { + USHORT Flags; + USHORT Length; + ULONG TimeStamp; + UNICODE_STRING DosPath; +} RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR; + +typedef struct _RTL_USER_PROCESS_PARAMETERS { + ULONG AllocationSize; + ULONG Size; + ULONG Flags; + ULONG DebugFlags; + HANDLE ConsoleHandle; + ULONG ConsoleFlags; + HANDLE hStdInput; + HANDLE hStdOutput; + HANDLE hStdError; + CURDIR CurrentDirectory; + UNICODE_STRING DllPath; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; + PWSTR Environment; + ULONG dwX; + ULONG dwY; + ULONG dwXSize; + ULONG dwYSize; + ULONG dwXCountChars; + ULONG dwYCountChars; + ULONG dwFillAttribute; + ULONG dwFlags; + ULONG wShowWindow; + UNICODE_STRING WindowTitle; + UNICODE_STRING Desktop; + UNICODE_STRING ShellInfo; + UNICODE_STRING RuntimeInfo; + RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[0x20]; + ULONG_PTR EnvironmentSize; + ULONG_PTR EnvironmentVersion; + PVOID PackageDependencyData; + ULONG ProcessGroupId; + ULONG LoaderThreads; +} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; + +typedef struct tagRTL_BITMAP { + ULONG SizeOfBitMap; + PULONG Buffer; +} RTL_BITMAP, *PRTL_BITMAP; + +typedef struct { + UINT next; + UINT id; + ULONGLONG addr; + ULONGLONG size; + UINT args[4]; +} CROSS_PROCESS_WORK_ENTRY; + +typedef union { + struct { + UINT first; + UINT counter; + }; + volatile LONGLONG hdr; +} CROSS_PROCESS_WORK_HDR; + +typedef struct { + CROSS_PROCESS_WORK_HDR free_list; + CROSS_PROCESS_WORK_HDR work_list; + ULONGLONG unknown[4]; + CROSS_PROCESS_WORK_ENTRY entries[1]; +} CROSS_PROCESS_WORK_LIST; + +typedef struct _CHPEV2_PROCESS_INFO { + ULONG Wow64ExecuteFlags; /* 000 */ + USHORT NativeMachineType; /* 004 */ + USHORT EmulatedMachineType; /* 006 */ + HANDLE SectionHandle; /* 008 */ + CROSS_PROCESS_WORK_LIST* CrossProcessWorkList; /* 010 */ + void* unknown; /* 018 */ +} CHPEV2_PROCESS_INFO, *PCHPEV2_PROCESS_INFO; + +typedef u64(__stdcall* KERNEL_CALLBACK_PROC)(void*, ULONG); + +typedef struct _PEB { /* win32/win64 */ + BOOLEAN InheritedAddressSpace; /* 000/000 */ + BOOLEAN ReadImageFileExecOptions; /* 001/001 */ + BOOLEAN BeingDebugged; /* 002/002 */ + UCHAR ImageUsedLargePages : 1; /* 003/003 */ + UCHAR IsProtectedProcess : 1; + UCHAR IsImageDynamicallyRelocated : 1; + UCHAR SkipPatchingUser32Forwarders : 1; + UCHAR IsPackagedProcess : 1; + UCHAR IsAppContainer : 1; + UCHAR IsProtectedProcessLight : 1; + UCHAR IsLongPathAwareProcess : 1; + HANDLE Mutant; /* 004/008 */ + HMODULE ImageBaseAddress; /* 008/010 */ + PPEB_LDR_DATA LdrData; /* 00c/018 */ + RTL_USER_PROCESS_PARAMETERS* ProcessParameters; /* 010/020 */ + PVOID SubSystemData; /* 014/028 */ + HANDLE ProcessHeap; /* 018/030 */ + PRTL_CRITICAL_SECTION FastPebLock; /* 01c/038 */ + PVOID AtlThunkSListPtr; /* 020/040 */ + PVOID IFEOKey; /* 024/048 */ + ULONG ProcessInJob : 1; /* 028/050 */ + ULONG ProcessInitializing : 1; + ULONG ProcessUsingVEH : 1; + ULONG ProcessUsingVCH : 1; + ULONG ProcessUsingFTH : 1; + ULONG ProcessPreviouslyThrottled : 1; + ULONG ProcessCurrentlyThrottled : 1; + ULONG ProcessImagesHotPatched : 1; + ULONG ReservedBits0 : 24; + KERNEL_CALLBACK_PROC* KernelCallbackTable; /* 02c/058 */ + ULONG Reserved; /* 030/060 */ + ULONG AtlThunkSListPtr32; /* 034/064 */ + PVOID ApiSetMap; /* 038/068 */ + ULONG TlsExpansionCounter; /* 03c/070 */ + PRTL_BITMAP TlsBitmap; /* 040/078 */ + ULONG TlsBitmapBits[2]; /* 044/080 */ + PVOID ReadOnlySharedMemoryBase; /* 04c/088 */ + PVOID SharedData; /* 050/090 */ + PVOID* ReadOnlyStaticServerData; /* 054/098 */ + PVOID AnsiCodePageData; /* 058/0a0 */ + PVOID OemCodePageData; /* 05c/0a8 */ + PVOID UnicodeCaseTableData; /* 060/0b0 */ + ULONG NumberOfProcessors; /* 064/0b8 */ + ULONG NtGlobalFlag; /* 068/0bc */ + LARGE_INTEGER CriticalSectionTimeout; /* 070/0c0 */ + SIZE_T HeapSegmentReserve; /* 078/0c8 */ + SIZE_T HeapSegmentCommit; /* 07c/0d0 */ + SIZE_T HeapDeCommitTotalFreeThreshold; /* 080/0d8 */ + SIZE_T HeapDeCommitFreeBlockThreshold; /* 084/0e0 */ + ULONG NumberOfHeaps; /* 088/0e8 */ + ULONG MaximumNumberOfHeaps; /* 08c/0ec */ + PVOID* ProcessHeaps; /* 090/0f0 */ + PVOID GdiSharedHandleTable; /* 094/0f8 */ + PVOID ProcessStarterHelper; /* 098/100 */ + PVOID GdiDCAttributeList; /* 09c/108 */ + PVOID LoaderLock; /* 0a0/110 */ + ULONG OSMajorVersion; /* 0a4/118 */ + ULONG OSMinorVersion; /* 0a8/11c */ + ULONG OSBuildNumber; /* 0ac/120 */ + ULONG OSPlatformId; /* 0b0/124 */ + ULONG ImageSubSystem; /* 0b4/128 */ + ULONG ImageSubSystemMajorVersion; /* 0b8/12c */ + ULONG ImageSubSystemMinorVersion; /* 0bc/130 */ + KAFFINITY ActiveProcessAffinityMask; /* 0c0/138 */ +#ifdef _WIN64 + ULONG GdiHandleBuffer[60]; /* /140 */ +#else + ULONG GdiHandleBuffer[34]; /* 0c4/ */ +#endif + PVOID PostProcessInitRoutine; /* 14c/230 */ + PRTL_BITMAP TlsExpansionBitmap; /* 150/238 */ + ULONG TlsExpansionBitmapBits[32]; /* 154/240 */ + ULONG SessionId; /* 1d4/2c0 */ + ULARGE_INTEGER AppCompatFlags; /* 1d8/2c8 */ + ULARGE_INTEGER AppCompatFlagsUser; /* 1e0/2d0 */ + PVOID ShimData; /* 1e8/2d8 */ + PVOID AppCompatInfo; /* 1ec/2e0 */ + UNICODE_STRING CSDVersion; /* 1f0/2e8 */ + PVOID ActivationContextData; /* 1f8/2f8 */ + PVOID ProcessAssemblyStorageMap; /* 1fc/300 */ + PVOID SystemDefaultActivationData; /* 200/308 */ + PVOID SystemAssemblyStorageMap; /* 204/310 */ + SIZE_T MinimumStackCommit; /* 208/318 */ + PVOID* FlsCallback; /* 20c/320 */ + LIST_ENTRY FlsListHead; /* 210/328 */ + union { + PRTL_BITMAP FlsBitmap; /* 218/338 */ +#ifdef _WIN64 + CHPEV2_PROCESS_INFO* ChpeV2ProcessInfo; /* /338 */ +#endif + }; + ULONG FlsBitmapBits[4]; /* 21c/340 */ + ULONG FlsHighIndex; /* 22c/350 */ + PVOID WerRegistrationData; /* 230/358 */ + PVOID WerShipAssertPtr; /* 234/360 */ + PVOID EcCodeBitMap; /* 238/368 */ + PVOID pImageHeaderHash; /* 23c/370 */ + ULONG HeapTracingEnabled : 1; /* 240/378 */ + ULONG CritSecTracingEnabled : 1; + ULONG LibLoaderTracingEnabled : 1; + ULONG SpareTracingBits : 29; + ULONGLONG CsrServerReadOnlySharedMemoryBase; /* 248/380 */ + ULONG TppWorkerpListLock; /* 250/388 */ + LIST_ENTRY TppWorkerpList; /* 254/390 */ + PVOID WaitOnAddressHashTable[0x80]; /* 25c/3a0 */ + PVOID TelemetryCoverageHeader; /* 45c/7a0 */ + ULONG CloudFileFlags; /* 460/7a8 */ + ULONG CloudFileDiagFlags; /* 464/7ac */ + CHAR PlaceholderCompatibilityMode; /* 468/7b0 */ + CHAR PlaceholderCompatibilityModeReserved[7]; /* 469/7b1 */ + PVOID LeapSecondData; /* 470/7b8 */ + ULONG LeapSecondFlags; /* 474/7c0 */ + ULONG NtGlobalFlag2; /* 478/7c4 */ +} PEB, *PPEB; + +typedef struct _RTL_ACTIVATION_CONTEXT_STACK_FRAME { + struct _RTL_ACTIVATION_CONTEXT_STACK_FRAME* Previous; + struct _ACTIVATION_CONTEXT* ActivationContext; + ULONG Flags; +} RTL_ACTIVATION_CONTEXT_STACK_FRAME, *PRTL_ACTIVATION_CONTEXT_STACK_FRAME; + +typedef struct _ACTIVATION_CONTEXT_STACK { + RTL_ACTIVATION_CONTEXT_STACK_FRAME* ActiveFrame; + LIST_ENTRY FrameListCache; + ULONG Flags; + ULONG NextCookieSequenceNumber; + ULONG_PTR StackId; +} ACTIVATION_CONTEXT_STACK, *PACTIVATION_CONTEXT_STACK; + +typedef struct _GDI_TEB_BATCH { + ULONG Offset; + HANDLE HDC; + ULONG Buffer[0x136]; +} GDI_TEB_BATCH; + +typedef struct _TEB_ACTIVE_FRAME_CONTEXT { + ULONG Flags; + const char* FrameName; +} TEB_ACTIVE_FRAME_CONTEXT, *PTEB_ACTIVE_FRAME_CONTEXT; + +typedef struct _TEB_ACTIVE_FRAME { + ULONG Flags; + struct _TEB_ACTIVE_FRAME* Previous; + TEB_ACTIVE_FRAME_CONTEXT* Context; +} TEB_ACTIVE_FRAME, *PTEB_ACTIVE_FRAME; + +typedef struct _TEB { /* win32/win64 */ + NT_TIB Tib; /* 000/0000 */ + PVOID EnvironmentPointer; /* 01c/0038 */ + CLIENT_ID ClientId; /* 020/0040 */ + PVOID ActiveRpcHandle; /* 028/0050 */ + PVOID ThreadLocalStoragePointer; /* 02c/0058 */ + PPEB Peb; /* 030/0060 */ + ULONG LastErrorValue; /* 034/0068 */ + ULONG CountOfOwnedCriticalSections; /* 038/006c */ + PVOID CsrClientThread; /* 03c/0070 */ + PVOID Win32ThreadInfo; /* 040/0078 */ + ULONG User32Reserved[26]; /* 044/0080 */ + ULONG UserReserved[5]; /* 0ac/00e8 */ + PVOID WOW32Reserved; /* 0c0/0100 */ + ULONG CurrentLocale; /* 0c4/0108 */ + ULONG FpSoftwareStatusRegister; /* 0c8/010c */ + PVOID ReservedForDebuggerInstrumentation[16]; /* 0cc/0110 */ +#ifdef _WIN64 + PVOID SystemReserved1[30]; /* /0190 */ +#else + PVOID SystemReserved1[26]; /* 10c/ */ +#endif + char PlaceholderCompatibilityMode; /* 174/0280 */ + BOOLEAN PlaceholderHydrationAlwaysExplicit; /* 175/0281 */ + char PlaceholderReserved[10]; /* 176/0282 */ + DWORD ProxiedProcessId; /* 180/028c */ + ACTIVATION_CONTEXT_STACK ActivationContextStack; /* 184/0290 */ + UCHAR WorkingOnBehalfOfTicket[8]; /* 19c/02b8 */ + LONG ExceptionCode; /* 1a4/02c0 */ + ACTIVATION_CONTEXT_STACK* ActivationContextStackPointer; /* 1a8/02c8 */ + ULONG_PTR InstrumentationCallbackSp; /* 1ac/02d0 */ + ULONG_PTR InstrumentationCallbackPreviousPc; /* 1b0/02d8 */ + ULONG_PTR InstrumentationCallbackPreviousSp; /* 1b4/02e0 */ +#ifdef _WIN64 + ULONG TxFsContext; /* /02e8 */ + BOOLEAN InstrumentationCallbackDisabled; /* /02ec */ + BOOLEAN UnalignedLoadStoreExceptions; /* /02ed */ +#else + BOOLEAN InstrumentationCallbackDisabled; /* 1b8/ */ + BYTE SpareBytes1[23]; /* 1b9/ */ + ULONG TxFsContext; /* 1d0/ */ +#endif + GDI_TEB_BATCH GdiTebBatch; /* 1d4/02f0 */ + CLIENT_ID RealClientId; /* 6b4/07d8 */ + HANDLE GdiCachedProcessHandle; /* 6bc/07e8 */ + ULONG GdiClientPID; /* 6c0/07f0 */ + ULONG GdiClientTID; /* 6c4/07f4 */ + PVOID GdiThreadLocaleInfo; /* 6c8/07f8 */ + ULONG_PTR Win32ClientInfo[62]; /* 6cc/0800 */ + PVOID glDispatchTable[233]; /* 7c4/09f0 */ + PVOID glReserved1[29]; /* b68/1138 */ + PVOID glReserved2; /* bdc/1220 */ + PVOID glSectionInfo; /* be0/1228 */ + PVOID glSection; /* be4/1230 */ + PVOID glTable; /* be8/1238 */ + PVOID glCurrentRC; /* bec/1240 */ + PVOID glContext; /* bf0/1248 */ + ULONG LastStatusValue; /* bf4/1250 */ + UNICODE_STRING StaticUnicodeString; /* bf8/1258 */ + WCHAR StaticUnicodeBuffer[261]; /* c00/1268 */ + PVOID DeallocationStack; /* e0c/1478 */ + PVOID TlsSlots[64]; /* e10/1480 */ + LIST_ENTRY TlsLinks; /* f10/1680 */ + PVOID Vdm; /* f18/1690 */ + PVOID ReservedForNtRpc; /* f1c/1698 */ + PVOID DbgSsReserved[2]; /* f20/16a0 */ + ULONG HardErrorMode; /* f28/16b0 */ +#ifdef _WIN64 + PVOID Instrumentation[11]; /* /16b8 */ +#else + PVOID Instrumentation[9]; /* f2c/ */ +#endif + GUID ActivityId; /* f50/1710 */ + PVOID SubProcessTag; /* f60/1720 */ + PVOID PerflibData; /* f64/1728 */ + PVOID EtwTraceData; /* f68/1730 */ + PVOID WinSockData; /* f6c/1738 */ + ULONG GdiBatchCount; /* f70/1740 */ + ULONG IdealProcessorValue; /* f74/1744 */ + ULONG GuaranteedStackBytes; /* f78/1748 */ + PVOID ReservedForPerf; /* f7c/1750 */ + PVOID ReservedForOle; /* f80/1758 */ + ULONG WaitingOnLoaderLock; /* f84/1760 */ + PVOID SavedPriorityState; /* f88/1768 */ + ULONG_PTR ReservedForCodeCoverage; /* f8c/1770 */ + PVOID ThreadPoolData; /* f90/1778 */ + PVOID* TlsExpansionSlots; /* f94/1780 */ +#ifdef _WIN64 + union { + PVOID DeallocationBStore; /* /1788 */ + PVOID* ChpeV2CpuAreaInfo; /* /1788 */ + } DUMMYUNIONNAME; + PVOID BStoreLimit; /* /1790 */ +#endif + ULONG MuiGeneration; /* f98/1798 */ + ULONG IsImpersonating; /* f9c/179c */ + PVOID NlsCache; /* fa0/17a0 */ + PVOID ShimData; /* fa4/17a8 */ + ULONG HeapVirtualAffinity; /* fa8/17b0 */ + PVOID CurrentTransactionHandle; /* fac/17b8 */ + TEB_ACTIVE_FRAME* ActiveFrame; /* fb0/17c0 */ + PVOID* FlsSlots; /* fb4/17c8 */ + PVOID PreferredLanguages; /* fb8/17d0 */ + PVOID UserPrefLanguages; /* fbc/17d8 */ + PVOID MergedPrefLanguages; /* fc0/17e0 */ + ULONG MuiImpersonation; /* fc4/17e8 */ + USHORT CrossTebFlags; /* fc8/17ec */ + USHORT SameTebFlags; /* fca/17ee */ + PVOID TxnScopeEnterCallback; /* fcc/17f0 */ + PVOID TxnScopeExitCallback; /* fd0/17f8 */ + PVOID TxnScopeContext; /* fd4/1800 */ + ULONG LockCount; /* fd8/1808 */ + LONG WowTebOffset; /* fdc/180c */ + PVOID ResourceRetValue; /* fe0/1810 */ + PVOID ReservedForWdf; /* fe4/1818 */ + ULONGLONG ReservedForCrt; /* fe8/1820 */ + GUID EffectiveContainerId; /* ff0/1828 */ +} TEB, *PTEB; +static_assert(offsetof(TEB, DeallocationStack) == + 0x1478); /* The only member we care about at the moment */ + +typedef enum _QUEUE_USER_APC_FLAGS { + QueueUserApcFlagsNone, + QueueUserApcFlagsSpecialUserApc, + QueueUserApcFlagsMaxValue +} QUEUE_USER_APC_FLAGS; + +typedef union _USER_APC_OPTION { + ULONG_PTR UserApcFlags; + HANDLE MemoryReserveHandle; +} USER_APC_OPTION, *PUSER_APC_OPTION; + +using PPS_APC_ROUTINE = void (*)(PVOID ApcArgument1, PVOID ApcArgument2, PVOID ApcArgument3, + PCONTEXT Context); + +typedef u64(__stdcall* NtClose_t)(HANDLE Handle); + +typedef u64(__stdcall* NtSetInformationFile_t)(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass); + +typedef u64(__stdcall* NtCreateThread_t)(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, + PCOBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle, + PCLIENT_ID ClientId, PCONTEXT ThreadContext, + PINITIAL_TEB InitialTeb, BOOLEAN CreateSuspended); + +typedef u64(__stdcall* NtTerminateThread_t)(HANDLE ThreadHandle, u64 ExitStatus); + +typedef u64(__stdcall* NtQueueApcThreadEx_t)(HANDLE ThreadHandle, + USER_APC_OPTION UserApcReserveHandle, + PPS_APC_ROUTINE ApcRoutine, PVOID ApcArgument1, + PVOID ApcArgument2, PVOID ApcArgument3); + +extern NtClose_t NtClose; +extern NtSetInformationFile_t NtSetInformationFile; +extern NtCreateThread_t NtCreateThread; +extern NtTerminateThread_t NtTerminateThread; +extern NtQueueApcThreadEx_t NtQueueApcThreadEx; + +namespace Common::NtApi { +void Initialize(); +} + +#endif diff --git a/src/common/spin_lock.cpp b/src/common/spin_lock.cpp index 9d4cfe36b..b2ef4ea1d 100755 --- a/src/common/spin_lock.cpp +++ b/src/common/spin_lock.cpp @@ -1,53 +1,53 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/spin_lock.h" - -#if _MSC_VER -#include -#if _M_AMD64 -#define __x86_64__ 1 -#endif -#if _M_ARM64 -#define __aarch64__ 1 -#endif -#else -#if __x86_64__ -#include -#endif -#endif - -namespace { - -void ThreadPause() { -#if __x86_64__ - _mm_pause(); -#elif __aarch64__ && _MSC_VER - __yield(); -#elif __aarch64__ - asm("yield"); -#endif -} - -} // Anonymous namespace - -namespace Common { - -void SpinLock::lock() { - while (lck.test_and_set(std::memory_order_acquire)) { - ThreadPause(); - } -} - -void SpinLock::unlock() { - lck.clear(std::memory_order_release); -} - -bool SpinLock::try_lock() { - if (lck.test_and_set(std::memory_order_acquire)) { - return false; - } - return true; -} - -} // namespace Common +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/spin_lock.h" + +#if _MSC_VER +#include +#if _M_AMD64 +#define __x86_64__ 1 +#endif +#if _M_ARM64 +#define __aarch64__ 1 +#endif +#else +#if __x86_64__ +#include +#endif +#endif + +namespace { + +void ThreadPause() { +#if __x86_64__ + _mm_pause(); +#elif __aarch64__ && _MSC_VER + __yield(); +#elif __aarch64__ + asm("yield"); +#endif +} + +} // Anonymous namespace + +namespace Common { + +void SpinLock::lock() { + while (lck.test_and_set(std::memory_order_acquire)) { + ThreadPause(); + } +} + +void SpinLock::unlock() { + lck.clear(std::memory_order_release); +} + +bool SpinLock::try_lock() { + if (lck.test_and_set(std::memory_order_acquire)) { + return false; + } + return true; +} + +} // namespace Common diff --git a/src/common/spin_lock.h b/src/common/spin_lock.h index 3229a8c6a..a83274851 100755 --- a/src/common/spin_lock.h +++ b/src/common/spin_lock.h @@ -1,33 +1,33 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -namespace Common { - -/** - * SpinLock class - * a lock similar to mutex that forces a thread to spin wait instead calling the - * supervisor. Should be used on short sequences of code. - */ -class SpinLock { -public: - SpinLock() = default; - - SpinLock(const SpinLock&) = delete; - SpinLock& operator=(const SpinLock&) = delete; - - SpinLock(SpinLock&&) = delete; - SpinLock& operator=(SpinLock&&) = delete; - - void lock(); - void unlock(); - [[nodiscard]] bool try_lock(); - -private: - std::atomic_flag lck = ATOMIC_FLAG_INIT; -}; - -} // namespace Common +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace Common { + +/** + * SpinLock class + * a lock similar to mutex that forces a thread to spin wait instead calling the + * supervisor. Should be used on short sequences of code. + */ +class SpinLock { +public: + SpinLock() = default; + + SpinLock(const SpinLock&) = delete; + SpinLock& operator=(const SpinLock&) = delete; + + SpinLock(SpinLock&&) = delete; + SpinLock& operator=(SpinLock&&) = delete; + + void lock(); + void unlock(); + [[nodiscard]] bool try_lock(); + +private: + std::atomic_flag lck = ATOMIC_FLAG_INIT; +}; + +} // namespace Common diff --git a/src/common/unique_function.h b/src/common/unique_function.h index 1891ec3c6..c15d88349 100755 --- a/src/common/unique_function.h +++ b/src/common/unique_function.h @@ -1,61 +1,61 @@ -// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -namespace Common { - -/// General purpose function wrapper similar to std::function. -/// Unlike std::function, the captured values don't have to be copyable. -/// This class can be moved but not copied. -template -class UniqueFunction { - class CallableBase { - public: - virtual ~CallableBase() = default; - virtual ResultType operator()(Args&&...) = 0; - }; - - template - class Callable final : public CallableBase { - public: - Callable(Functor&& functor_) : functor{std::move(functor_)} {} - ~Callable() override = default; - - ResultType operator()(Args&&... args) override { - return functor(std::forward(args)...); - } - - private: - Functor functor; - }; - -public: - UniqueFunction() = default; - - template - UniqueFunction(Functor&& functor) - : callable{std::make_unique>(std::move(functor))} {} - - UniqueFunction& operator=(UniqueFunction&& rhs) noexcept = default; - UniqueFunction(UniqueFunction&& rhs) noexcept = default; - - UniqueFunction& operator=(const UniqueFunction&) = delete; - UniqueFunction(const UniqueFunction&) = delete; - - ResultType operator()(Args&&... args) const { - return (*callable)(std::forward(args)...); - } - - explicit operator bool() const noexcept { - return static_cast(callable); - } - -private: - std::unique_ptr callable; -}; - -} // namespace Common +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace Common { + +/// General purpose function wrapper similar to std::function. +/// Unlike std::function, the captured values don't have to be copyable. +/// This class can be moved but not copied. +template +class UniqueFunction { + class CallableBase { + public: + virtual ~CallableBase() = default; + virtual ResultType operator()(Args&&...) = 0; + }; + + template + class Callable final : public CallableBase { + public: + Callable(Functor&& functor_) : functor{std::move(functor_)} {} + ~Callable() override = default; + + ResultType operator()(Args&&... args) override { + return functor(std::forward(args)...); + } + + private: + Functor functor; + }; + +public: + UniqueFunction() = default; + + template + UniqueFunction(Functor&& functor) + : callable{std::make_unique>(std::move(functor))} {} + + UniqueFunction& operator=(UniqueFunction&& rhs) noexcept = default; + UniqueFunction(UniqueFunction&& rhs) noexcept = default; + + UniqueFunction& operator=(const UniqueFunction&) = delete; + UniqueFunction(const UniqueFunction&) = delete; + + ResultType operator()(Args&&... args) const { + return (*callable)(std::forward(args)...); + } + + explicit operator bool() const noexcept { + return static_cast(callable); + } + +private: + std::unique_ptr callable; +}; + +} // namespace Common diff --git a/src/common/version.h b/src/common/version.h index 5e6599604..c903b1db6 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -8,7 +8,7 @@ namespace Common { -constexpr char VERSION[] = "0.4.1 WIP"; +constexpr char VERSION[] = "0.5.1 WIP"; constexpr bool isRelease = false; } // namespace Common diff --git a/src/core/crypto/crypto.cpp b/src/core/crypto/crypto.cpp index aa1c96724..00f1dea46 100644 --- a/src/core/crypto/crypto.cpp +++ b/src/core/crypto/crypto.cpp @@ -141,12 +141,9 @@ void Crypto::decryptEFSM(std::span NPcommID, std::span efsmIv, std::span ciphertext, std::span decrypted) { - std::vector TrophyKey = {0x21, 0xF4, 0x1A, 0x6B, 0xAD, 0x8A, 0x1D, 0x3E, - 0xCA, 0x7A, 0xD5, 0x86, 0xC1, 0x01, 0xB7, 0xA9}; std::vector TrophyIV = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // step 1: Encrypt NPcommID CryptoPP::CBC_Mode::Encryption encrypt; - encrypt.SetKeyWithIV(TrophyKey.data(), TrophyKey.size(), TrophyIV.data()); std::vector trpKey(16); diff --git a/src/core/debug_state.cpp b/src/core/debug_state.cpp index daf614bd9..6508a9875 100644 --- a/src/core/debug_state.cpp +++ b/src/core/debug_state.cpp @@ -11,6 +11,7 @@ #include "libraries/kernel/time.h" #include "libraries/system/msgdialog.h" #include "video_core/amdgpu/pm4_cmds.h" +#include "video_core/renderer_vulkan/vk_pipeline_cache.h" using namespace DebugStateType; @@ -168,8 +169,12 @@ void DebugStateImpl::PushRegsDump(uintptr_t base_addr, uintptr_t header_addr, if ((*dump)->regs.stage_enable.IsStageEnabled(i)) { auto stage = (*dump)->regs.ProgramForStage(i); if (stage->address_lo != 0) { + const auto& info = AmdGpu::Liverpool::SearchBinaryInfo(stage->Address()); auto code = stage->Code(); (*dump)->stages[i] = PipelineShaderProgramDump{ + .name = Vulkan::PipelineCache::GetShaderName(Shader::StageFromIndex(i), + info.shader_hash), + .hash = info.shader_hash, .user_data = *stage, .code = std::vector{code.begin(), code.end()}, }; @@ -191,7 +196,10 @@ void DebugStateImpl::PushRegsDumpCompute(uintptr_t base_addr, uintptr_t header_a auto& cs = (*dump)->regs.cs_program; cs = cs_state; + const auto& info = AmdGpu::Liverpool::SearchBinaryInfo(cs.Address()); (*dump)->cs_data = PipelineComputerProgramDump{ + .name = Vulkan::PipelineCache::GetShaderName(Shader::Stage::Compute, info.shader_hash), + .hash = info.shader_hash, .cs_program = cs, .code = std::vector{cs.Code().begin(), cs.Code().end()}, }; diff --git a/src/core/debug_state.h b/src/core/debug_state.h index a0e428b6b..6a8e15baa 100644 --- a/src/core/debug_state.h +++ b/src/core/debug_state.h @@ -50,11 +50,15 @@ struct QueueDump { }; struct PipelineShaderProgramDump { + std::string name; + u64 hash; Vulkan::Liverpool::ShaderProgram user_data{}; std::vector code{}; }; struct PipelineComputerProgramDump { + std::string name; + u64 hash; Vulkan::Liverpool::ComputeProgram cs_program{}; std::vector code{}; }; diff --git a/src/core/devtools/widget/cmd_list.cpp b/src/core/devtools/widget/cmd_list.cpp index 7c550cf2e..dc3eb9cdd 100644 --- a/src/core/devtools/widget/cmd_list.cpp +++ b/src/core/devtools/widget/cmd_list.cpp @@ -1174,7 +1174,7 @@ CmdListViewer::CmdListViewer(DebugStateType::FrameDump* _frame_dump, } } -void CmdListViewer::Draw(bool only_batches_view) { +void CmdListViewer::Draw(bool only_batches_view, CmdListFilter& filter) { const auto& ctx = *GetCurrentContext(); if (batch_view.open) { @@ -1285,6 +1285,41 @@ void CmdListViewer::Draw(bool only_batches_view) { } auto& batch = std::get(event); + + // filtering + { + bool remove = false; + + if (filter.shader_name[0] != '\0') { + remove = true; + std::string_view shader_name{filter.shader_name}; + const auto& data = frame_dump->regs.find(batch.command_addr); + if (data != frame_dump->regs.end()) { + DebugStateType::RegDump& dump = data->second; + if (dump.is_compute) { + if (dump.cs_data.name.contains(shader_name)) { + remove = false; + break; + } + } else { + for (int i = 0; i < DebugStateType::RegDump::MaxShaderStages; ++i) { + if (dump.regs.stage_enable.IsStageEnabled(i)) { + auto& stage = dump.stages[i]; + if (stage.name.contains(shader_name)) { + remove = false; + break; + } + } + } + } + } + } + + if (remove) { + continue; + } + } + auto const* pm4_hdr = reinterpret_cast(cmdb_addr + batch.start_addr); diff --git a/src/core/devtools/widget/cmd_list.h b/src/core/devtools/widget/cmd_list.h index ed71d0b76..e2c61f6b9 100644 --- a/src/core/devtools/widget/cmd_list.h +++ b/src/core/devtools/widget/cmd_list.h @@ -35,6 +35,10 @@ void ParseDepthControl(u32 value, bool begin_table = true); void ParseEqaa(u32 value, bool begin_table = true); void ParseZInfo(u32 value, bool begin_table = true); +struct CmdListFilter { + char shader_name[128]{}; +}; + class CmdListViewer { DebugStateType::FrameDump* frame_dump; @@ -70,7 +74,7 @@ public: explicit CmdListViewer(DebugStateType::FrameDump* frame_dump, const std::vector& cmd_list, uintptr_t base_addr = 0, std::string name = ""); - void Draw(bool only_batches_view = false); + void Draw(bool only_batches_view, CmdListFilter& filter); }; } // namespace Core::Devtools::Widget diff --git a/src/core/devtools/widget/common.h b/src/core/devtools/widget/common.h index 75eb55301..4684f6e3b 100644 --- a/src/core/devtools/widget/common.h +++ b/src/core/devtools/widget/common.h @@ -117,7 +117,7 @@ static bool IsDrawCall(AmdGpu::PM4ItOpcode opcode) { inline std::optional exec_cli(const char* cli) { std::array buffer{}; std::string output; - const auto f = popen(cli, "rt"); + const auto f = popen(cli, "r"); if (!f) { pclose(f); return {}; diff --git a/src/core/devtools/widget/frame_dump.cpp b/src/core/devtools/widget/frame_dump.cpp index 055ce1333..646ccb6d6 100644 --- a/src/core/devtools/widget/frame_dump.cpp +++ b/src/core/devtools/widget/frame_dump.cpp @@ -132,6 +132,15 @@ void FrameDumpViewer::Draw() { } } EndDisabled(); + SameLine(); + if (BeginMenu("Filter")) { + + TextUnformatted("Shader name"); + SameLine(); + InputText("##filter_shader", filter.shader_name, sizeof(filter.shader_name)); + + ImGui::EndMenu(); + } TextEx("Submit num"); SameLine(); @@ -187,7 +196,7 @@ void FrameDumpViewer::Draw() { EndGroup(); } if (is_showing && selected_cmd != -1) { - cmd_list_viewer[selected_cmd].Draw(is_collapsed); + cmd_list_viewer[selected_cmd].Draw(is_collapsed, filter); } End(); } diff --git a/src/core/devtools/widget/frame_dump.h b/src/core/devtools/widget/frame_dump.h index cc4fe6381..94075112b 100644 --- a/src/core/devtools/widget/frame_dump.h +++ b/src/core/devtools/widget/frame_dump.h @@ -27,6 +27,8 @@ class FrameDumpViewer { s32 selected_queue_num2; s32 selected_cmd = -1; + CmdListFilter filter; + public: bool is_open = true; diff --git a/src/core/devtools/widget/frame_graph.cpp b/src/core/devtools/widget/frame_graph.cpp index 952f50c34..0e170db38 100644 --- a/src/core/devtools/widget/frame_graph.cpp +++ b/src/core/devtools/widget/frame_graph.cpp @@ -19,6 +19,57 @@ constexpr float BAR_HEIGHT_MULT = 1.25f; constexpr float FRAME_GRAPH_PADDING_Y = 3.0f; constexpr static float FRAME_GRAPH_HEIGHT = 50.0f; +void FrameGraph::DrawFrameGraph() { + // Frame graph - inspired by + // https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times + const float full_width = GetContentRegionAvail().x; + auto pos = GetCursorScreenPos(); + const ImVec2 size{full_width, FRAME_GRAPH_HEIGHT + FRAME_GRAPH_PADDING_Y * 2.0f}; + ItemSize(size); + if (!ItemAdd({pos, pos + size}, GetID("FrameGraph"))) { + return; + } + + float target_dt = 1.0f / (TARGET_FPS * (float)Config::vblankDiv()); + float cur_pos_x = pos.x + full_width; + pos.y += FRAME_GRAPH_PADDING_Y; + const float final_pos_y = pos.y + FRAME_GRAPH_HEIGHT; + + auto& draw_list = *GetWindowDrawList(); + draw_list.AddRectFilled({pos.x, pos.y - FRAME_GRAPH_PADDING_Y}, + {pos.x + full_width, final_pos_y + FRAME_GRAPH_PADDING_Y}, + IM_COL32(0x33, 0x33, 0x33, 0xFF)); + draw_list.PushClipRect({pos.x, pos.y}, {pos.x + full_width, final_pos_y}, true); + for (u32 i = 0; i < FRAME_BUFFER_SIZE; ++i) { + const auto& frame_info = frame_list[(DebugState.GetFrameNum() - i) % FRAME_BUFFER_SIZE]; + const float dt_factor = target_dt / frame_info.delta; + + const float width = std::ceil(BAR_WIDTH_MULT / dt_factor); + const float height = + std::min(std::log2(BAR_HEIGHT_MULT / dt_factor) / 3.0f, 1.0f) * FRAME_GRAPH_HEIGHT; + + ImU32 color; + if (dt_factor >= 0.95f) { // BLUE + color = IM_COL32(0x33, 0x33, 0xFF, 0xFF); + } else if (dt_factor >= 0.5f) { // GREEN <> YELLOW + float t = 1.0f - (dt_factor - 0.5f) * 2.0f; + int r = (int)(0xFF * t); + color = IM_COL32(r, 0xFF, 0, 0xFF); + } else { // YELLOW <> RED + float t = dt_factor * 2.0f; + int g = (int)(0xFF * t); + color = IM_COL32(0xFF, g, 0, 0xFF); + } + draw_list.AddRectFilled({cur_pos_x - width, final_pos_y - height}, {cur_pos_x, final_pos_y}, + color); + cur_pos_x -= width; + if (cur_pos_x < width) { + break; + } + } + draw_list.PopClipRect(); +} + void FrameGraph::Draw() { if (!is_open) { return; @@ -43,55 +94,9 @@ void FrameGraph::Draw() { Text("Frame time: %.3f ms (%.1f FPS)", deltaTime, frameRate); Text("Flip frame: %d Gnm submit frame: %d", DebugState.flip_frame_count.load(), DebugState.gnm_frame_count.load()); + SeparatorText("Frame graph"); - - const float full_width = GetContentRegionAvail().x; - // Frame graph - inspired by - // https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times - auto pos = GetCursorScreenPos(); - const ImVec2 size{full_width, FRAME_GRAPH_HEIGHT + FRAME_GRAPH_PADDING_Y * 2.0f}; - ItemSize(size); - if (!ItemAdd({pos, pos + size}, GetID("FrameGraph"))) { - return; - } - - float target_dt = 1.0f / (TARGET_FPS * (float)Config::vblankDiv()); - float cur_pos_x = pos.x + full_width; - pos.y += FRAME_GRAPH_PADDING_Y; - const float final_pos_y = pos.y + FRAME_GRAPH_HEIGHT; - - draw_list.AddRectFilled({pos.x, pos.y - FRAME_GRAPH_PADDING_Y}, - {pos.x + full_width, final_pos_y + FRAME_GRAPH_PADDING_Y}, - IM_COL32(0x33, 0x33, 0x33, 0xFF)); - draw_list.PushClipRect({pos.x, pos.y}, {pos.x + full_width, final_pos_y}, true); - for (u32 i = 0; i < FRAME_BUFFER_SIZE; ++i) { - const auto& frame_info = frame_list[(DebugState.GetFrameNum() - i) % FRAME_BUFFER_SIZE]; - const float dt_factor = target_dt / frame_info.delta; - - const float width = std::ceil(BAR_WIDTH_MULT / dt_factor); - const float height = - std::min(std::log2(BAR_HEIGHT_MULT / dt_factor) / 3.0f, 1.0f) * FRAME_GRAPH_HEIGHT; - - ImU32 color; - if (dt_factor >= 0.95f) { // BLUE - color = IM_COL32(0x33, 0x33, 0xFF, 0xFF); - } else if (dt_factor >= 0.5f) { // GREEN <> YELLOW - float t = 1.0f - (dt_factor - 0.5f) * 2.0f; - int r = (int)(0xFF * t); - color = IM_COL32(r, 0xFF, 0, 0xFF); - } else { // YELLOW <> RED - float t = dt_factor * 2.0f; - int g = (int)(0xFF * t); - color = IM_COL32(0xFF, g, 0, 0xFF); - } - draw_list.AddRectFilled({cur_pos_x - width, final_pos_y - height}, - {cur_pos_x, final_pos_y}, color); - cur_pos_x -= width; - if (cur_pos_x < width) { - break; - } - } - draw_list.PopClipRect(); + DrawFrameGraph(); } End(); } diff --git a/src/core/devtools/widget/frame_graph.h b/src/core/devtools/widget/frame_graph.h index 700b6b2a2..40a68ffa7 100644 --- a/src/core/devtools/widget/frame_graph.h +++ b/src/core/devtools/widget/frame_graph.h @@ -16,6 +16,8 @@ class FrameGraph { std::array frame_list{}; + void DrawFrameGraph(); + public: bool is_open = true; diff --git a/src/core/devtools/widget/reg_view.cpp b/src/core/devtools/widget/reg_view.cpp index 79b02a849..a1b7937df 100644 --- a/src/core/devtools/widget/reg_view.cpp +++ b/src/core/devtools/widget/reg_view.cpp @@ -292,6 +292,17 @@ void RegView::Draw() { EndMenuBar(); } + const char* shader_name = "_"; + if (data.is_compute) { + shader_name = data.cs_data.name.c_str(); + } else if (selected_shader >= 0) { + shader_name = data.stages[selected_shader].name.c_str(); + } + + TextUnformatted("Shader: "); + SameLine(); + TextUnformatted(shader_name); + if (!data.is_compute && BeginChild("STAGES", {}, ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeY)) { diff --git a/src/core/devtools/widget/shader_list.cpp b/src/core/devtools/widget/shader_list.cpp index 2c97db7fd..97d01896d 100644 --- a/src/core/devtools/widget/shader_list.cpp +++ b/src/core/devtools/widget/shader_list.cpp @@ -112,6 +112,10 @@ bool ShaderList::Selection::DrawShader(DebugStateType::ShaderDump& value) { ReloadShader(value); } } + SameLine(); + if (Button("Copy name")) { + SetClipboardText(value.name.c_str()); + } if (value.is_patched) { if (BeginCombo("Shader type", showing_bin ? "SPIRV" : "GLSL", @@ -229,9 +233,16 @@ void ShaderList::Draw() { return; } + InputTextEx("##search_shader", "Search by name", search_box, sizeof(search_box), {}, + ImGuiInputTextFlags_None); + auto width = GetContentRegionAvail().x; int i = 0; for (const auto& shader : DebugState.shader_dump_list) { + if (search_box[0] != '\0' && !shader.name.contains(search_box)) { + i++; + continue; + } char name[128]; if (shader.is_patched) { snprintf(name, sizeof(name), "%s (PATCH ON)", shader.name.c_str()); diff --git a/src/core/devtools/widget/shader_list.h b/src/core/devtools/widget/shader_list.h index 2534ded35..fbb8d2070 100644 --- a/src/core/devtools/widget/shader_list.h +++ b/src/core/devtools/widget/shader_list.h @@ -31,6 +31,8 @@ class ShaderList { std::vector open_shaders{}; + char search_box[128]{}; + public: bool open = false; diff --git a/src/core/libraries/app_content/app_content.cpp b/src/core/libraries/app_content/app_content.cpp index ca3cdad39..1d23e7f44 100644 --- a/src/core/libraries/app_content/app_content.cpp +++ b/src/core/libraries/app_content/app_content.cpp @@ -145,8 +145,10 @@ int PS4_SYSV_ABI sceAppContentDownloadDataFormat() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAppContentDownloadDataGetAvailableSpaceKb() { +int PS4_SYSV_ABI sceAppContentDownloadDataGetAvailableSpaceKb(OrbisAppContentMountPoint* mountPoint, + u64* availableSpaceKb) { LOG_ERROR(Lib_AppContent, "(STUBBED) called"); + *availableSpaceKb = 1048576; return ORBIS_OK; } @@ -294,9 +296,9 @@ int PS4_SYSV_ABI sceAppContentTemporaryDataFormat() { } int PS4_SYSV_ABI sceAppContentTemporaryDataGetAvailableSpaceKb( - const OrbisAppContentMountPoint* mountPoint, size_t* availableSpaceKb) { + const OrbisAppContentMountPoint* mountPoint, u64* availableSpaceKb) { LOG_ERROR(Lib_AppContent, "(STUBBED) called"); - *availableSpaceKb = 1073741824; + *availableSpaceKb = 1048576; return ORBIS_OK; } diff --git a/src/core/libraries/app_content/app_content.h b/src/core/libraries/app_content/app_content.h index f41f7dccf..05bd3bc49 100644 --- a/src/core/libraries/app_content/app_content.h +++ b/src/core/libraries/app_content/app_content.h @@ -84,7 +84,8 @@ int PS4_SYSV_ABI sceAppContentDownload0Shrink(); int PS4_SYSV_ABI sceAppContentDownload1Expand(); int PS4_SYSV_ABI sceAppContentDownload1Shrink(); int PS4_SYSV_ABI sceAppContentDownloadDataFormat(); -int PS4_SYSV_ABI sceAppContentDownloadDataGetAvailableSpaceKb(); +int PS4_SYSV_ABI sceAppContentDownloadDataGetAvailableSpaceKb(OrbisAppContentMountPoint* mountPoint, + u64* availableSpaceKb); int PS4_SYSV_ABI sceAppContentGetAddcontDownloadProgress(); int PS4_SYSV_ABI sceAppContentGetAddcontInfo(u32 service_label, const OrbisNpUnifiedEntitlementLabel* entitlementLabel, @@ -105,7 +106,7 @@ int PS4_SYSV_ABI sceAppContentSmallSharedDataMount(); int PS4_SYSV_ABI sceAppContentSmallSharedDataUnmount(); int PS4_SYSV_ABI sceAppContentTemporaryDataFormat(); int PS4_SYSV_ABI sceAppContentTemporaryDataGetAvailableSpaceKb( - const OrbisAppContentMountPoint* mountPoint, size_t* availableSpaceKb); + const OrbisAppContentMountPoint* mountPoint, u64* availableSpaceKb); int PS4_SYSV_ABI sceAppContentTemporaryDataMount(); int PS4_SYSV_ABI sceAppContentTemporaryDataMount2(OrbisAppContentTemporaryDataOption option, OrbisAppContentMountPoint* mountPoint); diff --git a/src/core/libraries/fiber/fiber.cpp b/src/core/libraries/fiber/fiber.cpp index c28d8d734..7bb81b61e 100644 --- a/src/core/libraries/fiber/fiber.cpp +++ b/src/core/libraries/fiber/fiber.cpp @@ -1,281 +1,484 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "fiber.h" - -#include "common/logging/log.h" -#include "core/libraries/fiber/fiber_error.h" -#include "core/libraries/libs.h" -#include "core/tls.h" - -#ifdef _WIN64 -#include -#endif - -namespace Libraries::Fiber { - -static constexpr u64 kFiberSignature = 0x054ad954; - -thread_local SceFiber* gCurrentFiber = nullptr; -thread_local void* gFiberThread = nullptr; - -void FiberEntry(void* param) { - SceFiber* fiber = static_cast(param); - u64 argRun = 0; - u64 argRet = 0; - - gCurrentFiber = fiber; - - if (fiber->pArgRun != nullptr) { - argRun = *fiber->pArgRun; - } - - Core::ExecuteGuest(fiber->entry, fiber->argOnInitialize, argRun); - UNREACHABLE(); -} - -s32 PS4_SYSV_ABI sceFiberInitialize(SceFiber* fiber, const char* name, SceFiberEntry entry, - u64 argOnInitialize, void* addrContext, u64 sizeContext, - const SceFiberOptParam* optParam) { - LOG_INFO(Lib_Fiber, "called: name = {}", name); - - if (!fiber || !name || !entry) { - return ORBIS_FIBER_ERROR_NULL; - } - - fiber->signature = kFiberSignature; - - fiber->entry = entry; - fiber->argOnInitialize = argOnInitialize; - - fiber->argRun = 0; - fiber->pArgRun = &fiber->argRun; - fiber->argReturn = 0; - fiber->pArgReturn = &fiber->argReturn; - - fiber->sizeContext = sizeContext; - - fiber->state = FiberState::Init; -#ifdef _WIN64 - fiber->handle = CreateFiber(sizeContext, FiberEntry, fiber); -#else - UNREACHABLE_MSG("Missing implementation"); -#endif - strncpy(fiber->name, name, ORBIS_FIBER_MAX_NAME_LENGTH); - - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFiberOptParamInitialize(SceFiberOptParam* optParam) { - LOG_ERROR(Lib_Fiber, "called"); - - if (!optParam) { - return ORBIS_FIBER_ERROR_NULL; - } - - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFiberFinalize(SceFiber* fiber) { - LOG_TRACE(Lib_Fiber, "called"); - - if (!fiber) { - return ORBIS_FIBER_ERROR_NULL; - } - if ((u64)fiber % 8 != 0) { - return ORBIS_FIBER_ERROR_ALIGNMENT; - } - if (fiber->signature != kFiberSignature) { - return ORBIS_FIBER_ERROR_INVALID; - } - if (fiber->state != FiberState::Run) { - return ORBIS_FIBER_ERROR_STATE; - } - - fiber->signature = 0; - fiber->state = FiberState::None; - -#ifdef _WIN64 - DeleteFiber(fiber->handle); -#else - UNREACHABLE_MSG("Missing implementation"); -#endif - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFiberRun(SceFiber* fiber, u64 argOnRunTo, u64* argOnReturn) { - LOG_TRACE(Lib_Fiber, "called"); - - if (!fiber) { - return ORBIS_FIBER_ERROR_NULL; - } - if ((u64)fiber % 8 != 0) { - return ORBIS_FIBER_ERROR_ALIGNMENT; - } - if (fiber->signature != kFiberSignature) { - return ORBIS_FIBER_ERROR_INVALID; - } - if (fiber->state == FiberState::Run) { - return ORBIS_FIBER_ERROR_STATE; - } - - if (gFiberThread == nullptr) { -#ifdef _WIN64 - gFiberThread = ConvertThreadToFiber(nullptr); -#else - UNREACHABLE_MSG("Missing implementation"); -#endif - } - - gCurrentFiber = fiber; - - if (fiber->pArgRun != nullptr) { - *fiber->pArgRun = argOnRunTo; - } - - fiber->pArgReturn = argOnReturn; - fiber->state = FiberState::Run; -#ifdef _WIN64 - SwitchToFiber(fiber->handle); -#else - UNREACHABLE_MSG("Missing implementation"); -#endif - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFiberSwitch(SceFiber* fiber, u64 argOnRunTo, u64* argOnRun) { - LOG_TRACE(Lib_Fiber, "called"); - - if (!fiber) { - return ORBIS_FIBER_ERROR_NULL; - } - if ((u64)fiber % 8 != 0) { - return ORBIS_FIBER_ERROR_ALIGNMENT; - } - if (fiber->signature != kFiberSignature) { - return ORBIS_FIBER_ERROR_INVALID; - } - if (gCurrentFiber == nullptr) { - return ORBIS_FIBER_ERROR_PERMISSION; - } - if (fiber->state == FiberState::Run) { - return ORBIS_FIBER_ERROR_STATE; - } - - gCurrentFiber->state = FiberState::Suspend; - - // TODO: argOnRun - - *fiber->pArgRun = argOnRunTo; - fiber->state = FiberState::Run; - - gCurrentFiber = fiber; -#ifdef _WIN64 - SwitchToFiber(fiber->handle); -#else - UNREACHABLE_MSG("Missing implementation"); -#endif - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFiberGetSelf(SceFiber** fiber) { - LOG_TRACE(Lib_Fiber, "called"); - - if (!fiber || !gCurrentFiber) { - return ORBIS_FIBER_ERROR_NULL; - } - if (gCurrentFiber->signature != kFiberSignature) { - return ORBIS_FIBER_ERROR_PERMISSION; - } - - *fiber = gCurrentFiber; - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFiberReturnToThread(u64 argOnReturn, u64* argOnRun) { - LOG_TRACE(Lib_Fiber, "called"); - - if (gCurrentFiber->signature != kFiberSignature) { - return ORBIS_FIBER_ERROR_PERMISSION; - } - - if (gCurrentFiber->pArgReturn != nullptr) { - *gCurrentFiber->pArgReturn = argOnReturn; - } - - // TODO: argOnRun - gCurrentFiber->state = FiberState::Suspend; - gCurrentFiber = nullptr; -#ifdef _WIN64 - SwitchToFiber(gFiberThread); -#else - UNREACHABLE_MSG("Missing implementation"); -#endif - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFiberGetInfo(SceFiber* fiber, SceFiberInfo* fiberInfo) { - LOG_INFO(Lib_Fiber, "called"); - - if (!fiber || !fiberInfo) { - return ORBIS_FIBER_ERROR_NULL; - } - - fiberInfo->entry = fiber->entry; - fiberInfo->argOnInitialize = fiber->argOnInitialize; - fiberInfo->addrContext = nullptr; - fiberInfo->sizeContext = fiber->sizeContext; - fiberInfo->sizeContextMargin = 0; - - strncpy(fiberInfo->name, fiber->name, ORBIS_FIBER_MAX_NAME_LENGTH); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFiberStartContextSizeCheck(u32 flags) { - LOG_ERROR(Lib_Fiber, "called"); - - if (flags != 0) { - return ORBIS_FIBER_ERROR_INVALID; - } - - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFiberStopContextSizeCheck() { - LOG_ERROR(Lib_Fiber, "called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFiberRename(SceFiber* fiber, const char* name) { - LOG_INFO(Lib_Fiber, "called, name = {}", name); - - if (!fiber || !name) { - return ORBIS_FIBER_ERROR_NULL; - } - if ((u64)fiber % 8 != 0) { - return ORBIS_FIBER_ERROR_ALIGNMENT; - } - - strncpy(fiber->name, name, ORBIS_FIBER_MAX_NAME_LENGTH); - return ORBIS_OK; -} - -void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym) { - LIB_FUNCTION("hVYD7Ou2pCQ", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberInitialize); - LIB_FUNCTION("asjUJJ+aa8s", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberOptParamInitialize); - LIB_FUNCTION("JeNX5F-NzQU", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberFinalize); - - LIB_FUNCTION("a0LLrZWac0M", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberRun); - LIB_FUNCTION("PFT2S-tJ7Uk", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberSwitch); - LIB_FUNCTION("p+zLIOg27zU", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberGetSelf); - LIB_FUNCTION("B0ZX2hx9DMw", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberReturnToThread); - - LIB_FUNCTION("uq2Y5BFz0PE", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberGetInfo); - LIB_FUNCTION("Lcqty+QNWFc", "libSceFiber", 1, "libSceFiber", 1, 1, - sceFiberStartContextSizeCheck); - LIB_FUNCTION("Kj4nXMpnM8Y", "libSceFiber", 1, "libSceFiber", 1, 1, - sceFiberStopContextSizeCheck); - LIB_FUNCTION("JzyT91ucGDc", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberRename); -} - -} // namespace Libraries::Fiber +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "fiber.h" + +#include "common/logging/log.h" +#include "core/libraries/fiber/fiber_error.h" +#include "core/libraries/libs.h" +#include "core/tls.h" + +namespace Libraries::Fiber { + +static constexpr u32 kFiberSignature0 = 0xdef1649c; +static constexpr u32 kFiberSignature1 = 0xb37592a0; +static constexpr u32 kFiberOptSignature = 0xbb40e64d; +static constexpr u64 kFiberStackSignature = 0x7149f2ca7149f2ca; +static constexpr u64 kFiberStackSizeCheck = 0xdeadbeefdeadbeef; + +static std::atomic context_size_check = false; + +OrbisFiberContext* GetFiberContext() { + return Core::GetTcbBase()->tcb_fiber; +} + +extern "C" s32 PS4_SYSV_ABI _sceFiberSetJmp(OrbisFiberContext* ctx) asm("_sceFiberSetJmp"); +extern "C" s32 PS4_SYSV_ABI _sceFiberLongJmp(OrbisFiberContext* ctx) asm("_sceFiberLongJmp"); +extern "C" void PS4_SYSV_ABI _sceFiberSwitchEntry(OrbisFiberData* data, + bool set_fpu) asm("_sceFiberSwitchEntry"); +extern "C" void PS4_SYSV_ABI _sceFiberForceQuit(u64 ret) asm("_sceFiberForceQuit"); + +extern "C" void PS4_SYSV_ABI _sceFiberForceQuit(u64 ret) { + OrbisFiberContext* g_ctx = GetFiberContext(); + g_ctx->return_val = ret; + _sceFiberLongJmp(g_ctx); +} + +void PS4_SYSV_ABI _sceFiberCheckStackOverflow(OrbisFiberContext* ctx) { + u64* stack_base = reinterpret_cast(ctx->current_fiber->addr_context); + if (stack_base && *stack_base != kFiberStackSignature) { + UNREACHABLE_MSG("Stack overflow detected in fiber."); + } +} + +void PS4_SYSV_ABI _sceFiberSwitchToFiber(OrbisFiber* fiber, u64 arg_on_run_to, + OrbisFiberContext* ctx) { + OrbisFiberContext* fiber_ctx = fiber->context; + if (fiber_ctx) { + ctx->arg_on_run_to = arg_on_run_to; + _sceFiberLongJmp(fiber_ctx); + __builtin_trap(); + } + + OrbisFiberData data{}; + if (ctx->prev_fiber) { + OrbisFiber* prev_fiber = ctx->prev_fiber; + ctx->prev_fiber = nullptr; + data.state = reinterpret_cast(&prev_fiber->state); + } else { + data.state = nullptr; + } + + data.entry = fiber->entry; + data.arg_on_initialize = fiber->arg_on_initialize; + data.arg_on_run_to = arg_on_run_to; + data.stack_addr = + reinterpret_cast(reinterpret_cast(fiber->addr_context) + fiber->size_context); + if (fiber->flags & FiberFlags::SetFpuRegs) { + data.fpucw = 0x037f; + data.mxcsr = 0x9fc0; + _sceFiberSwitchEntry(&data, true); + } else { + _sceFiberSwitchEntry(&data, false); + } + + __builtin_trap(); +} + +void PS4_SYSV_ABI _sceFiberSwitch(OrbisFiber* cur_fiber, OrbisFiber* fiber, u64 arg_on_run_to, + OrbisFiberContext* ctx) { + ctx->prev_fiber = cur_fiber; + ctx->current_fiber = fiber; + + if (fiber->addr_context == nullptr) { + ctx->prev_fiber = nullptr; + + OrbisFiberData data{}; + data.entry = fiber->entry; + data.arg_on_initialize = fiber->arg_on_initialize; + data.arg_on_run_to = arg_on_run_to; + data.stack_addr = reinterpret_cast(ctx->rsp & ~15); + data.state = reinterpret_cast(&cur_fiber->state); + + if (fiber->flags & FiberFlags::SetFpuRegs) { + data.fpucw = 0x037f; + data.mxcsr = 0x9fc0; + _sceFiberSwitchEntry(&data, true); + } else { + _sceFiberSwitchEntry(&data, false); + } + + __builtin_trap(); + } + + _sceFiberSwitchToFiber(fiber, arg_on_run_to, ctx); + __builtin_trap(); +} + +void PS4_SYSV_ABI _sceFiberTerminate(OrbisFiber* fiber, u64 arg_on_return, OrbisFiberContext* ctx) { + ctx->arg_on_return = arg_on_return; + _sceFiberLongJmp(ctx); + __builtin_trap(); +} + +s32 PS4_SYSV_ABI sceFiberInitialize(OrbisFiber* fiber, const char* name, OrbisFiberEntry entry, + u64 arg_on_initialize, void* addr_context, u64 size_context, + const OrbisFiberOptParam* opt_param, u32 build_ver) { + if (!fiber || !name || !entry) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)fiber & 7 || (u64)addr_context & 15) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (opt_param && (u64)opt_param & 7) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (size_context && size_context < ORBIS_FIBER_CONTEXT_MINIMUM_SIZE) { + return ORBIS_FIBER_ERROR_RANGE; + } + if (size_context & 15) { + return ORBIS_FIBER_ERROR_INVALID; + } + if (!addr_context && size_context) { + return ORBIS_FIBER_ERROR_INVALID; + } + if (addr_context && !size_context) { + return ORBIS_FIBER_ERROR_INVALID; + } + if (opt_param && opt_param->magic != kFiberOptSignature) { + return ORBIS_FIBER_ERROR_INVALID; + } + + u32 flags = FiberFlags::None; + if (build_ver >= 0x3500000) { + flags |= FiberFlags::SetFpuRegs; + } + if (context_size_check) { + flags |= FiberFlags::ContextSizeCheck; + } + + strncpy(fiber->name, name, ORBIS_FIBER_MAX_NAME_LENGTH); + + fiber->entry = entry; + fiber->arg_on_initialize = arg_on_initialize; + fiber->addr_context = addr_context; + fiber->size_context = size_context; + fiber->context = nullptr; + fiber->flags = flags; + + /* + A low stack area is problematic, as we can easily + cause a stack overflow with our HLE. + */ + if (size_context && size_context <= 4096) { + LOG_WARNING(Lib_Fiber, "Fiber initialized with small stack area."); + } + + fiber->magic_start = kFiberSignature0; + fiber->magic_end = kFiberSignature1; + + if (addr_context != nullptr) { + fiber->context_start = addr_context; + fiber->context_end = + reinterpret_cast(reinterpret_cast(addr_context) + size_context); + + /* Apply signature to start of stack */ + *(u64*)addr_context = kFiberStackSignature; + + if (flags & FiberFlags::ContextSizeCheck) { + u64* stack_start = reinterpret_cast(fiber->context_start); + u64* stack_end = reinterpret_cast(fiber->context_end); + + u64* stack_ptr = stack_start + 1; + while (stack_ptr < stack_end) { + *stack_ptr++ = kFiberStackSizeCheck; + } + } + } + + fiber->state = FiberState::Idle; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberOptParamInitialize(OrbisFiberOptParam* opt_param) { + if (!opt_param) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)opt_param & 7) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + + opt_param->magic = kFiberOptSignature; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberFinalize(OrbisFiber* fiber) { + if (!fiber) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)fiber & 7) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (fiber->magic_start != kFiberSignature0 || fiber->magic_end != kFiberSignature1) { + return ORBIS_FIBER_ERROR_INVALID; + } + + FiberState expected = FiberState::Idle; + if (!fiber->state.compare_exchange_strong(expected, FiberState::Terminated)) { + return ORBIS_FIBER_ERROR_STATE; + } + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberRun(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_return) { + if (!fiber) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)fiber & 7) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (fiber->magic_start != kFiberSignature0 || fiber->magic_end != kFiberSignature1) { + return ORBIS_FIBER_ERROR_INVALID; + } + + Core::Tcb* tcb = Core::GetTcbBase(); + if (tcb->tcb_fiber) { + return ORBIS_FIBER_ERROR_PERMISSION; + } + + FiberState expected = FiberState::Idle; + if (!fiber->state.compare_exchange_strong(expected, FiberState::Run)) { + return ORBIS_FIBER_ERROR_STATE; + } + + OrbisFiberContext ctx{}; + ctx.current_fiber = fiber; + ctx.prev_fiber = nullptr; + ctx.return_val = 0; + + tcb->tcb_fiber = &ctx; + + s32 jmp = _sceFiberSetJmp(&ctx); + if (!jmp) { + if (fiber->addr_context) { + _sceFiberSwitchToFiber(fiber, arg_on_run_to, &ctx); + __builtin_trap(); + } + + OrbisFiberData data{}; + data.entry = fiber->entry; + data.arg_on_initialize = fiber->arg_on_initialize; + data.arg_on_run_to = arg_on_run_to; + data.stack_addr = reinterpret_cast(ctx.rsp & ~15); + data.state = nullptr; + if (fiber->flags & FiberFlags::SetFpuRegs) { + data.fpucw = 0x037f; + data.mxcsr = 0x9fc0; + _sceFiberSwitchEntry(&data, true); + } else { + _sceFiberSwitchEntry(&data, false); + } + } + + OrbisFiber* cur_fiber = ctx.current_fiber; + ctx.current_fiber = nullptr; + cur_fiber->state = FiberState::Idle; + + if (ctx.return_val != 0) { + /* Fiber entry returned! This should never happen. */ + UNREACHABLE_MSG("Fiber entry function returned."); + } + + if (arg_on_return) { + *arg_on_return = ctx.arg_on_return; + } + + tcb->tcb_fiber = nullptr; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberSwitch(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_run) { + if (!fiber) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)fiber & 7) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (fiber->magic_start != kFiberSignature0 || fiber->magic_end != kFiberSignature1) { + return ORBIS_FIBER_ERROR_INVALID; + } + + OrbisFiberContext* g_ctx = GetFiberContext(); + if (!g_ctx) { + return ORBIS_FIBER_ERROR_PERMISSION; + } + + FiberState expected = FiberState::Idle; + if (!fiber->state.compare_exchange_strong(expected, FiberState::Run)) { + return ORBIS_FIBER_ERROR_STATE; + } + + OrbisFiber* cur_fiber = g_ctx->current_fiber; + if (cur_fiber->addr_context == nullptr) { + _sceFiberSwitch(cur_fiber, fiber, arg_on_run_to, g_ctx); + __builtin_trap(); + } + + OrbisFiberContext ctx{}; + s32 jmp = _sceFiberSetJmp(&ctx); + if (!jmp) { + cur_fiber->context = &ctx; + _sceFiberCheckStackOverflow(g_ctx); + _sceFiberSwitch(cur_fiber, fiber, arg_on_run_to, g_ctx); + __builtin_trap(); + } + + g_ctx = GetFiberContext(); + if (g_ctx->prev_fiber) { + g_ctx->prev_fiber->state = FiberState::Idle; + g_ctx->prev_fiber = nullptr; + } + + if (arg_on_run) { + *arg_on_run = g_ctx->arg_on_run_to; + } + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberGetSelf(OrbisFiber** fiber) { + if (!fiber) { + return ORBIS_FIBER_ERROR_NULL; + } + + OrbisFiberContext* g_ctx = GetFiberContext(); + if (!g_ctx) { + return ORBIS_FIBER_ERROR_PERMISSION; + } + + *fiber = g_ctx->current_fiber; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberReturnToThread(u64 arg_on_return, u64* arg_on_run) { + OrbisFiberContext* g_ctx = GetFiberContext(); + if (!g_ctx) { + return ORBIS_FIBER_ERROR_PERMISSION; + } + + OrbisFiber* cur_fiber = g_ctx->current_fiber; + if (cur_fiber->addr_context) { + OrbisFiberContext ctx{}; + s32 jmp = _sceFiberSetJmp(&ctx); + if (jmp) { + g_ctx = GetFiberContext(); + if (g_ctx->prev_fiber) { + g_ctx->prev_fiber->state = FiberState::Idle; + g_ctx->prev_fiber = nullptr; + } + if (arg_on_run) { + *arg_on_run = g_ctx->arg_on_run_to; + } + return ORBIS_OK; + } + + cur_fiber->context = &ctx; + _sceFiberCheckStackOverflow(g_ctx); + } + + _sceFiberTerminate(cur_fiber, arg_on_return, g_ctx); + __builtin_trap(); +} + +s32 PS4_SYSV_ABI sceFiberGetInfo(OrbisFiber* fiber, OrbisFiberInfo* fiber_info) { + if (!fiber || !fiber_info) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)fiber & 7 || (u64)fiber_info & 7) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (fiber_info->size != sizeof(OrbisFiberInfo)) { + return ORBIS_FIBER_ERROR_INVALID; + } + if (fiber->magic_start != kFiberSignature0 || fiber->magic_end != kFiberSignature1) { + return ORBIS_FIBER_ERROR_INVALID; + } + + fiber_info->entry = fiber->entry; + fiber_info->arg_on_initialize = fiber->arg_on_initialize; + fiber_info->addr_context = fiber->addr_context; + fiber_info->size_context = fiber->size_context; + strncpy(fiber_info->name, fiber->name, ORBIS_FIBER_MAX_NAME_LENGTH); + + fiber_info->size_context_margin = -1; + if (fiber->flags & FiberFlags::ContextSizeCheck && fiber->addr_context != nullptr) { + u64 stack_margin = 0; + u64* stack_start = reinterpret_cast(fiber->context_start); + u64* stack_end = reinterpret_cast(fiber->context_end); + + if (*stack_start == kFiberStackSignature) { + u64* stack_ptr = stack_start + 1; + while (stack_ptr < stack_end) { + if (*stack_ptr == kFiberStackSizeCheck) { + stack_ptr++; + } + } + + stack_margin = + reinterpret_cast(stack_ptr) - reinterpret_cast(stack_start + 1); + } + + fiber_info->size_context_margin = stack_margin; + } + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberStartContextSizeCheck(u32 flags) { + if (flags != 0) { + return ORBIS_FIBER_ERROR_INVALID; + } + + u32 expected = 0; + if (!context_size_check.compare_exchange_strong(expected, 1u)) { + return ORBIS_FIBER_ERROR_STATE; + } + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberStopContextSizeCheck() { + u32 expected = 1; + if (!context_size_check.compare_exchange_strong(expected, 0u)) { + return ORBIS_FIBER_ERROR_STATE; + } + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFiberRename(OrbisFiber* fiber, const char* name) { + if (!fiber || !name) { + return ORBIS_FIBER_ERROR_NULL; + } + if ((u64)fiber & 7) { + return ORBIS_FIBER_ERROR_ALIGNMENT; + } + if (fiber->magic_start != kFiberSignature0 || fiber->magic_end != kFiberSignature1) { + return ORBIS_FIBER_ERROR_INVALID; + } + + strncpy(fiber->name, name, ORBIS_FIBER_MAX_NAME_LENGTH); + return ORBIS_OK; +} + +void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("hVYD7Ou2pCQ", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberInitialize); + LIB_FUNCTION("7+OJIpko9RY", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberInitialize); + LIB_FUNCTION("asjUJJ+aa8s", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberOptParamInitialize); + LIB_FUNCTION("JeNX5F-NzQU", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberFinalize); + + LIB_FUNCTION("a0LLrZWac0M", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberRun); + LIB_FUNCTION("PFT2S-tJ7Uk", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberSwitch); + LIB_FUNCTION("p+zLIOg27zU", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberGetSelf); + LIB_FUNCTION("B0ZX2hx9DMw", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberReturnToThread); + + LIB_FUNCTION("uq2Y5BFz0PE", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberGetInfo); + LIB_FUNCTION("Lcqty+QNWFc", "libSceFiber", 1, "libSceFiber", 1, 1, + sceFiberStartContextSizeCheck); + LIB_FUNCTION("Kj4nXMpnM8Y", "libSceFiber", 1, "libSceFiber", 1, 1, + sceFiberStopContextSizeCheck); + LIB_FUNCTION("JzyT91ucGDc", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberRename); +} + +} // namespace Libraries::Fiber diff --git a/src/core/libraries/fiber/fiber.h b/src/core/libraries/fiber/fiber.h index 00099f93b..3c4e3b70e 100644 --- a/src/core/libraries/fiber/fiber.h +++ b/src/core/libraries/fiber/fiber.h @@ -1,78 +1,118 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/assert.h" -#include "common/types.h" - -namespace Core::Loader { -class SymbolsResolver; -} -namespace Libraries::Fiber { - -#define ORBIS_FIBER_MAX_NAME_LENGTH (31) - -typedef void PS4_SYSV_ABI (*SceFiberEntry)(u64 argOnInitialize, u64 argOnRun); - -enum FiberState : u32 { - None = 0u, - Init = 1u, - Run = 2u, - Suspend = 3u, -}; - -struct SceFiber { - u64 signature; - FiberState state; - SceFiberEntry entry; - u64 argOnInitialize; - u64 argRun; - u64* pArgRun; - u64 argReturn; - u64* pArgReturn; - u64 sizeContext; - char name[ORBIS_FIBER_MAX_NAME_LENGTH]; - void* handle; -}; -static_assert(sizeof(SceFiber) <= 256); - -struct SceFiberInfo { - u64 size; - SceFiberEntry entry; - u64 argOnInitialize; - void* addrContext; - u64 sizeContext; - char name[ORBIS_FIBER_MAX_NAME_LENGTH + 1]; - u64 sizeContextMargin; -}; -static_assert(sizeof(SceFiberInfo) <= 128); - -using SceFiberOptParam = void*; - -s32 PS4_SYSV_ABI sceFiberInitialize(SceFiber* fiber, const char* name, SceFiberEntry entry, - u64 argOnInitialize, void* addrContext, u64 sizeContext, - const SceFiberOptParam* optParam); - -s32 PS4_SYSV_ABI sceFiberOptParamInitialize(SceFiberOptParam* optParam); - -s32 PS4_SYSV_ABI sceFiberFinalize(SceFiber* fiber); - -s32 PS4_SYSV_ABI sceFiberRun(SceFiber* fiber, u64 argOnRunTo, u64* argOnReturn); - -s32 PS4_SYSV_ABI sceFiberSwitch(SceFiber* fiber, u64 argOnRunTo, u64* argOnRun); - -s32 PS4_SYSV_ABI sceFiberGetSelf(SceFiber** fiber); - -s32 PS4_SYSV_ABI sceFiberReturnToThread(u64 argOnReturn, u64* argOnRun); - -s32 PS4_SYSV_ABI sceFiberGetInfo(SceFiber* fiber, SceFiberInfo* fiberInfo); - -s32 PS4_SYSV_ABI sceFiberStartContextSizeCheck(u32 flags); - -s32 PS4_SYSV_ABI sceFiberStopContextSizeCheck(void); - -s32 PS4_SYSV_ABI sceFiberRename(SceFiber* fiber, const char* name); - -void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym); +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/assert.h" +#include "common/types.h" + +#include + +namespace Core::Loader { +class SymbolsResolver; +} +namespace Libraries::Fiber { + +#define ORBIS_FIBER_MAX_NAME_LENGTH (31) +#define ORBIS_FIBER_CONTEXT_MINIMUM_SIZE (512) + +typedef void PS4_SYSV_ABI (*OrbisFiberEntry)(u64 arg_on_initialize, u64 arg_on_run); + +enum FiberState : u32 { + Run = 1u, + Idle = 2u, + Terminated = 3u, +}; + +enum FiberFlags : u32 { + None = 0x0, + NoUlobjmgr = 0x1, + ContextSizeCheck = 0x10, + SetFpuRegs = 0x100, +}; + +struct OrbisFiber; + +struct OrbisFiberContext { + struct { + u64 rax, rcx, rdx, rbx, rsp, rbp, r8, r9, r10, r11, r12, r13, r14, r15; + u16 fpucw; + u32 mxcsr; + }; + OrbisFiber* current_fiber; + OrbisFiber* prev_fiber; + u64 arg_on_run_to; + u64 arg_on_return; + u64 return_val; +}; + +struct OrbisFiberData { + OrbisFiberEntry entry; + u64 arg_on_initialize; + u64 arg_on_run_to; + void* stack_addr; + u32* state; + u16 fpucw; + s8 pad[2]; + u32 mxcsr; +}; + +struct OrbisFiber { + u32 magic_start; + std::atomic state; + OrbisFiberEntry entry; + u64 arg_on_initialize; + void* addr_context; + u64 size_context; + char name[ORBIS_FIBER_MAX_NAME_LENGTH + 1]; + OrbisFiberContext* context; + u32 flags; + void* context_start; + void* context_end; + u32 magic_end; +}; +static_assert(sizeof(OrbisFiber) <= 256); + +struct OrbisFiberInfo { + u64 size; + OrbisFiberEntry entry; + u64 arg_on_initialize; + void* addr_context; + u64 size_context; + char name[ORBIS_FIBER_MAX_NAME_LENGTH + 1]; + u64 size_context_margin; + u8 pad[48]; +}; +static_assert(sizeof(OrbisFiberInfo) == 128); + +struct OrbisFiberOptParam { + u32 magic; +}; +static_assert(sizeof(OrbisFiberOptParam) <= 128); + +s32 PS4_SYSV_ABI sceFiberInitialize(OrbisFiber* fiber, const char* name, OrbisFiberEntry entry, + u64 arg_on_initialize, void* addr_context, u64 size_context, + const OrbisFiberOptParam* opt_param, u32 build_version); + +s32 PS4_SYSV_ABI sceFiberOptParamInitialize(OrbisFiberOptParam* opt_param); + +s32 PS4_SYSV_ABI sceFiberFinalize(OrbisFiber* fiber); + +s32 PS4_SYSV_ABI sceFiberRun(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_return); + +s32 PS4_SYSV_ABI sceFiberSwitch(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_run); + +s32 PS4_SYSV_ABI sceFiberGetSelf(OrbisFiber** fiber); + +s32 PS4_SYSV_ABI sceFiberReturnToThread(u64 arg_on_return, u64* arg_on_run); + +s32 PS4_SYSV_ABI sceFiberGetInfo(OrbisFiber* fiber, OrbisFiberInfo* fiber_info); + +s32 PS4_SYSV_ABI sceFiberStartContextSizeCheck(u32 flags); + +s32 PS4_SYSV_ABI sceFiberStopContextSizeCheck(void); + +s32 PS4_SYSV_ABI sceFiberRename(OrbisFiber* fiber, const char* name); + +void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Fiber \ No newline at end of file diff --git a/src/core/libraries/fiber/fiber_context.s b/src/core/libraries/fiber/fiber_context.s new file mode 100644 index 000000000..04fe93581 --- /dev/null +++ b/src/core/libraries/fiber/fiber_context.s @@ -0,0 +1,121 @@ +# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +.global _sceFiberSetJmp +_sceFiberSetJmp: + movq %rax, 0x0(%rdi) + + movq (%rsp), %rdx + movq %rdx, 0x10(%rdi) + + movq %rcx, 0x08(%rdi) + movq %rbx, 0x18(%rdi) + movq %rsp, 0x20(%rdi) + movq %rbp, 0x28(%rdi) + + movq %r8, 0x30(%rdi) + movq %r9, 0x38(%rdi) + movq %r10, 0x40(%rdi) + movq %r11, 0x48(%rdi) + movq %r12, 0x50(%rdi) + movq %r13, 0x58(%rdi) + movq %r14, 0x60(%rdi) + movq %r15, 0x68(%rdi) + + fnstcw 0x70(%rdi) + stmxcsr 0x72(%rdi) + + xor %eax, %eax + ret + +.global _sceFiberLongJmp +_sceFiberLongJmp: + # MXCSR = (MXCSR & 0x3f) ^ (ctx->mxcsr & ~0x3f) + stmxcsr -0x4(%rsp) + movl 0x72(%rdi), %eax + andl $0xffffffc0, %eax + movl -0x4(%rsp), %ecx + andl $0x3f, %ecx + xorl %eax, %ecx + movl %ecx, -0x4(%rsp) + ldmxcsr -0x4(%rsp) + + movq 0x00(%rdi), %rax + movq 0x08(%rdi), %rcx + movq 0x10(%rdi), %rdx + movq 0x18(%rdi), %rbx + movq 0x20(%rdi), %rsp + movq 0x28(%rdi), %rbp + + movq 0x30(%rdi), %r8 + movq 0x38(%rdi), %r9 + movq 0x40(%rdi), %r10 + movq 0x48(%rdi), %r11 + movq 0x50(%rdi), %r12 + movq 0x58(%rdi), %r13 + movq 0x60(%rdi), %r14 + movq 0x68(%rdi), %r15 + + fldcw 0x70(%rdi) + + # Make the jump and return 1 + movq %rdx, 0x00(%rsp) + movl $0x1, %eax + ret + +.global _sceFiberSwitchEntry +_sceFiberSwitchEntry: + mov %rdi, %r11 + + # Set stack address to provided stack + movq 0x18(%r11), %rsp + xorl %ebp, %ebp + + movq 0x20(%r11), %r10 # data->state + + # Set previous fiber state to Idle + test %r10, %r10 + jz .clear_regs + movl $2, (%r10) + +.clear_regs: + test %esi, %esi + jz .skip_fpu_regs + + ldmxcsr 0x2c(%r11) + fldcw 0x28(%r11) + +.skip_fpu_regs: + movq 0x08(%r11), %rdi # data->arg_on_initialize + movq 0x10(%r11), %rsi # data->arg_on_run_to + movq 0x00(%r11), %r11 # data->entry + + xorl %eax, %eax + xorl %ebx, %ebx + xorl %ecx, %ecx + xorl %edx, %edx + xorq %r8, %r8 + xorq %r9, %r9 + xorq %r10, %r10 + xorq %r12, %r12 + xorq %r13, %r13 + xorq %r14, %r14 + xorq %r15, %r15 + pxor %mm0, %mm0 + pxor %mm1, %mm1 + pxor %mm2, %mm2 + pxor %mm3, %mm3 + pxor %mm4, %mm4 + pxor %mm5, %mm5 + pxor %mm6, %mm6 + pxor %mm7, %mm7 + emms + vzeroall + + # Call the fiber's entry function: entry(arg_on_initialize, arg_on_run_to) + call *%r11 + + # Fiber returned, not good + movl $1, %edi + call _sceFiberForceQuit + ret \ No newline at end of file diff --git a/src/core/libraries/ime/ime_common.h b/src/core/libraries/ime/ime_common.h index 6d4afd81d..96f073dc5 100644 --- a/src/core/libraries/ime/ime_common.h +++ b/src/core/libraries/ime/ime_common.h @@ -1,183 +1,183 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" -#include "core/libraries/rtc/rtc.h" - -enum class OrbisImeType : u32 { - Default = 0, - BasicLatin = 1, - Url = 2, - Mail = 3, - Number = 4, -}; - -enum class OrbisImeHorizontalAlignment : u32 { - Left = 0, - Center = 1, - Right = 2, -}; - -enum class OrbisImeVerticalAlignment : u32 { - Top = 0, - Center = 1, - Bottom = 2, -}; - -enum class OrbisImeEnterLabel : u32 { - Default = 0, - Send = 1, - Search = 2, - Go = 3, -}; - -enum class OrbisImeInputMethod : u32 { - Default = 0, -}; - -enum class OrbisImeEventId : u32 { - Open = 0, - UpdateText = 1, - UpdateCaret = 2, - PressClose = 4, - PressEnter = 5, - Abort = 6, - CandidateListStart = 7, - CandidateListEnd = 8, - CandidateWord = 9, - CandidateIndex = 10, - CandidateDone = 11, - CandidateCancel = 12, - ChangeDevice = 14, - ChangeInputMethodState = 18, - - KeyboardOpen = 256, - KeyboardKeycodeDoen = 257, - KeyboardKeycodeUp = 258, - KeyboardKeycodeRepeat = 259, - KeyboardConnection = 260, - KeyboardDisconnection = 261, - KeyboardAbort = 262, -}; - -enum class OrbisImeKeyboardType : u32 { - NONE = 0, - DANISH = 1, - GERMAN = 2, - GERMAN_SW = 3, - ENGLISH_US = 4, - ENGLISH_GB = 5, - SPANISH = 6, - SPANISH_LA = 7, - FINNISH = 8, - FRENCH = 9, - FRENCH_BR = 10, - FRENCH_CA = 11, - FRENCH_SW = 12, - ITALIAN = 13, - DUTCH = 14, - NORWEGIAN = 15, - POLISH = 16, - PORTUGUESE_BR = 17, - PORTUGUESE_PT = 18, - RUSSIAN = 19, - SWEDISH = 20, - TURKISH = 21, - JAPANESE_ROMAN = 22, - JAPANESE_KANA = 23, - KOREAN = 24, - SM_CHINESE = 25, - TR_CHINESE_ZY = 26, - TR_CHINESE_PY_HK = 27, - TR_CHINESE_PY_TW = 28, - TR_CHINESE_CG = 29, - ARABIC_AR = 30, - THAI = 31, - CZECH = 32, - GREEK = 33, - INDONESIAN = 34, - VIETNAMESE = 35, - ROMANIAN = 36, - HUNGARIAN = 37, -}; - -enum class OrbisImeDeviceType : u32 { - None = 0, - Controller = 1, - ExtKeyboard = 2, - RemoteOsk = 3, -}; - -struct OrbisImeRect { - f32 x; - f32 y; - u32 width; - u32 height; -}; - -struct OrbisImeTextAreaProperty { - u32 mode; // OrbisImeTextAreaMode - u32 index; - s32 length; -}; - -struct OrbisImeEditText { - char16_t* str; - u32 caret_index; - u32 area_num; - OrbisImeTextAreaProperty text_area[4]; -}; - -struct OrbisImeKeycode { - u16 keycode; - char16_t character; - u32 status; - OrbisImeKeyboardType type; - s32 user_id; - u32 resource_id; - Libraries::Rtc::OrbisRtcTick timestamp; -}; - -struct OrbisImeKeyboardResourceIdArray { - s32 userId; - u32 resourceId[5]; -}; - -enum class OrbisImeCaretMovementDirection : u32 { - Still = 0, - Left = 1, - Right = 2, - Up = 3, - Down = 4, - Home = 5, - End = 6, - PageUp = 7, - PageDown = 8, - Top = 9, - Bottom = 10, -}; - -union OrbisImeEventParam { - OrbisImeRect rect; - OrbisImeEditText text; - OrbisImeCaretMovementDirection caret_move; - OrbisImeKeycode keycode; - OrbisImeKeyboardResourceIdArray resource_id_array; - char16_t* candidate_word; - s32 candidate_index; - OrbisImeDeviceType device_type; - u32 input_method_state; - s8 reserved[64]; -}; - -struct OrbisImeEvent { - OrbisImeEventId id; - OrbisImeEventParam param; -}; - -using OrbisImeTextFilter = PS4_SYSV_ABI int (*)(char16_t* outText, u32* outTextLength, - const char16_t* srcText, u32 srcTextLength); - -using OrbisImeEventHandler = PS4_SYSV_ABI void (*)(void* arg, const OrbisImeEvent* e); +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "core/libraries/rtc/rtc.h" + +enum class OrbisImeType : u32 { + Default = 0, + BasicLatin = 1, + Url = 2, + Mail = 3, + Number = 4, +}; + +enum class OrbisImeHorizontalAlignment : u32 { + Left = 0, + Center = 1, + Right = 2, +}; + +enum class OrbisImeVerticalAlignment : u32 { + Top = 0, + Center = 1, + Bottom = 2, +}; + +enum class OrbisImeEnterLabel : u32 { + Default = 0, + Send = 1, + Search = 2, + Go = 3, +}; + +enum class OrbisImeInputMethod : u32 { + Default = 0, +}; + +enum class OrbisImeEventId : u32 { + Open = 0, + UpdateText = 1, + UpdateCaret = 2, + PressClose = 4, + PressEnter = 5, + Abort = 6, + CandidateListStart = 7, + CandidateListEnd = 8, + CandidateWord = 9, + CandidateIndex = 10, + CandidateDone = 11, + CandidateCancel = 12, + ChangeDevice = 14, + ChangeInputMethodState = 18, + + KeyboardOpen = 256, + KeyboardKeycodeDoen = 257, + KeyboardKeycodeUp = 258, + KeyboardKeycodeRepeat = 259, + KeyboardConnection = 260, + KeyboardDisconnection = 261, + KeyboardAbort = 262, +}; + +enum class OrbisImeKeyboardType : u32 { + NONE = 0, + DANISH = 1, + GERMAN = 2, + GERMAN_SW = 3, + ENGLISH_US = 4, + ENGLISH_GB = 5, + SPANISH = 6, + SPANISH_LA = 7, + FINNISH = 8, + FRENCH = 9, + FRENCH_BR = 10, + FRENCH_CA = 11, + FRENCH_SW = 12, + ITALIAN = 13, + DUTCH = 14, + NORWEGIAN = 15, + POLISH = 16, + PORTUGUESE_BR = 17, + PORTUGUESE_PT = 18, + RUSSIAN = 19, + SWEDISH = 20, + TURKISH = 21, + JAPANESE_ROMAN = 22, + JAPANESE_KANA = 23, + KOREAN = 24, + SM_CHINESE = 25, + TR_CHINESE_ZY = 26, + TR_CHINESE_PY_HK = 27, + TR_CHINESE_PY_TW = 28, + TR_CHINESE_CG = 29, + ARABIC_AR = 30, + THAI = 31, + CZECH = 32, + GREEK = 33, + INDONESIAN = 34, + VIETNAMESE = 35, + ROMANIAN = 36, + HUNGARIAN = 37, +}; + +enum class OrbisImeDeviceType : u32 { + None = 0, + Controller = 1, + ExtKeyboard = 2, + RemoteOsk = 3, +}; + +struct OrbisImeRect { + f32 x; + f32 y; + u32 width; + u32 height; +}; + +struct OrbisImeTextAreaProperty { + u32 mode; // OrbisImeTextAreaMode + u32 index; + s32 length; +}; + +struct OrbisImeEditText { + char16_t* str; + u32 caret_index; + u32 area_num; + OrbisImeTextAreaProperty text_area[4]; +}; + +struct OrbisImeKeycode { + u16 keycode; + char16_t character; + u32 status; + OrbisImeKeyboardType type; + s32 user_id; + u32 resource_id; + Libraries::Rtc::OrbisRtcTick timestamp; +}; + +struct OrbisImeKeyboardResourceIdArray { + s32 userId; + u32 resourceId[5]; +}; + +enum class OrbisImeCaretMovementDirection : u32 { + Still = 0, + Left = 1, + Right = 2, + Up = 3, + Down = 4, + Home = 5, + End = 6, + PageUp = 7, + PageDown = 8, + Top = 9, + Bottom = 10, +}; + +union OrbisImeEventParam { + OrbisImeRect rect; + OrbisImeEditText text; + OrbisImeCaretMovementDirection caret_move; + OrbisImeKeycode keycode; + OrbisImeKeyboardResourceIdArray resource_id_array; + char16_t* candidate_word; + s32 candidate_index; + OrbisImeDeviceType device_type; + u32 input_method_state; + s8 reserved[64]; +}; + +struct OrbisImeEvent { + OrbisImeEventId id; + OrbisImeEventParam param; +}; + +using OrbisImeTextFilter = PS4_SYSV_ABI int (*)(char16_t* outText, u32* outTextLength, + const char16_t* srcText, u32 srcTextLength); + +using OrbisImeEventHandler = PS4_SYSV_ABI void (*)(void* arg, const OrbisImeEvent* e); diff --git a/src/core/libraries/ime/ime_ui.cpp b/src/core/libraries/ime/ime_ui.cpp index 8eaa48178..37f25e200 100644 --- a/src/core/libraries/ime/ime_ui.cpp +++ b/src/core/libraries/ime/ime_ui.cpp @@ -1,253 +1,253 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "ime_ui.h" -#include "imgui/imgui_std.h" - -namespace Libraries::Ime { - -using namespace ImGui; - -static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; - -ImeState::ImeState(const OrbisImeParam* param) { - if (!param) { - return; - } - - work_buffer = param->work; - text_buffer = param->inputTextBuffer; - - std::size_t text_len = std::char_traits::length(text_buffer); - if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(), - ORBIS_IME_MAX_TEXT_LENGTH * 4)) { - LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); - } -} - -ImeState::ImeState(ImeState&& other) noexcept - : work_buffer(other.work_buffer), text_buffer(other.text_buffer), - current_text(std::move(other.current_text)), event_queue(std::move(other.event_queue)) { - other.text_buffer = nullptr; -} - -ImeState& ImeState::operator=(ImeState&& other) noexcept { - if (this != &other) { - work_buffer = other.work_buffer; - text_buffer = other.text_buffer; - current_text = std::move(other.current_text); - event_queue = std::move(other.event_queue); - - other.text_buffer = nullptr; - } - return *this; -} - -void ImeState::SendEvent(OrbisImeEvent* event) { - std::unique_lock lock{queue_mutex}; - event_queue.push(*event); -} - -void ImeState::SendEnterEvent() { - OrbisImeEvent enterEvent{}; - enterEvent.id = OrbisImeEventId::PressEnter; - SendEvent(&enterEvent); -} - -void ImeState::SendCloseEvent() { - OrbisImeEvent closeEvent{}; - closeEvent.id = OrbisImeEventId::PressClose; - closeEvent.param.text.str = reinterpret_cast(work_buffer); - SendEvent(&closeEvent); -} - -void ImeState::SetText(const char16_t* text, u32 length) {} - -void ImeState::SetCaret(u32 position) {} - -bool ImeState::ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len, - char* utf8_text, std::size_t utf8_text_len) { - std::fill(utf8_text, utf8_text + utf8_text_len, '\0'); - const ImWchar* orbis_text_ptr = reinterpret_cast(orbis_text); - ImTextStrToUtf8(utf8_text, utf8_text_len, orbis_text_ptr, orbis_text_ptr + orbis_text_len); - - return true; -} - -bool ImeState::ConvertUTF8ToOrbis(const char* utf8_text, std::size_t utf8_text_len, - char16_t* orbis_text, std::size_t orbis_text_len) { - std::fill(orbis_text, orbis_text + orbis_text_len, u'\0'); - ImTextStrFromUtf8(reinterpret_cast(orbis_text), orbis_text_len, utf8_text, nullptr); - - return true; -} - -ImeUi::ImeUi(ImeState* state, const OrbisImeParam* param) : state(state), ime_param(param) { - if (param) { - AddLayer(this); - } -} - -ImeUi::~ImeUi() { - std::scoped_lock lock(draw_mutex); - Free(); -} - -ImeUi& ImeUi::operator=(ImeUi&& other) { - std::scoped_lock lock(draw_mutex, other.draw_mutex); - Free(); - - state = other.state; - ime_param = other.ime_param; - first_render = other.first_render; - other.state = nullptr; - other.ime_param = nullptr; - - AddLayer(this); - return *this; -} - -void ImeUi::Draw() { - std::unique_lock lock{draw_mutex}; - - if (!state) { - return; - } - - const auto& ctx = *GetCurrentContext(); - const auto& io = ctx.IO; - - // TODO: Figure out how to properly translate the positions - - // for example, if a game wants to center the IME panel, - // we have to translate the panel position in a way that it - // still becomes centered, as the game normally calculates - // the position assuming a it's running on a 1920x1080 screen, - // whereas we are running on a 1280x720 window size (by default). - // - // e.g. Panel position calculation from a game: - // param.posx = (1920 / 2) - (panelWidth / 2); - // param.posy = (1080 / 2) - (panelHeight / 2); - const auto size = GetIO().DisplaySize; - f32 pos_x = (ime_param->posx / 1920.0f * (float)size.x); - f32 pos_y = (ime_param->posy / 1080.0f * (float)size.y); - - ImVec2 window_pos = {pos_x, pos_y}; - ImVec2 window_size = {500.0f, 100.0f}; - - // SetNextWindowPos(window_pos); - SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), - ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); - SetNextWindowSize(window_size); - SetNextWindowCollapsed(false); - - if (first_render || !io.NavActive) { - SetNextWindowFocus(); - } - - if (Begin("IME##Ime", nullptr, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoSavedSettings)) { - DrawPrettyBackground(); - - DrawInputText(); - SetCursorPosY(GetCursorPosY() + 10.0f); - - const char* button_text; - button_text = "Done##ImeDone"; - - float button_spacing = 10.0f; - float total_button_width = BUTTON_SIZE.x * 2 + button_spacing; - float button_start_pos = (window_size.x - total_button_width) / 2.0f; - - SetCursorPosX(button_start_pos); - - if (Button(button_text, BUTTON_SIZE) || (IsKeyPressed(ImGuiKey_Enter))) { - state->SendEnterEvent(); - } - - SameLine(0.0f, button_spacing); - - if (Button("Close##ImeClose", BUTTON_SIZE)) { - state->SendCloseEvent(); - } - } - End(); - - first_render = false; -} - -void ImeUi::DrawInputText() { - ImVec2 input_size = {GetWindowWidth() - 40.0f, 0.0f}; - SetCursorPosX(20.0f); - if (first_render) { - SetKeyboardFocusHere(); - } - if (InputTextEx("##ImeInput", nullptr, state->current_text.begin(), ime_param->maxTextLength, - input_size, ImGuiInputTextFlags_CallbackAlways, InputTextCallback, this)) { - } -} - -int ImeUi::InputTextCallback(ImGuiInputTextCallbackData* data) { - ImeUi* ui = static_cast(data->UserData); - ASSERT(ui); - - static std::string lastText; - std::string currentText(data->Buf, data->BufTextLen); - if (currentText != lastText) { - OrbisImeEditText eventParam{}; - eventParam.str = reinterpret_cast(ui->ime_param->work); - eventParam.caret_index = data->CursorPos; - eventParam.area_num = 1; - - eventParam.text_area[0].mode = 1; // Edit mode - eventParam.text_area[0].index = data->CursorPos; - eventParam.text_area[0].length = data->BufTextLen; - - if (!ui->state->ConvertUTF8ToOrbis(data->Buf, data->BufTextLen, eventParam.str, - ui->ime_param->maxTextLength)) { - LOG_ERROR(Lib_ImeDialog, "Failed to convert Orbis char to UTF-8"); - return 0; - } - - if (!ui->state->ConvertUTF8ToOrbis(data->Buf, data->BufTextLen, - ui->ime_param->inputTextBuffer, - ui->ime_param->maxTextLength)) { - LOG_ERROR(Lib_ImeDialog, "Failed to convert Orbis char to UTF-8"); - return 0; - } - - OrbisImeEvent event{}; - event.id = OrbisImeEventId::UpdateText; - event.param.text = eventParam; - - lastText = currentText; - ui->state->SendEvent(&event); - } - - static int lastCaretPos = -1; - if (lastCaretPos == -1) { - lastCaretPos = data->CursorPos; - } else if (data->CursorPos != lastCaretPos) { - OrbisImeCaretMovementDirection caretDirection = OrbisImeCaretMovementDirection::Still; - if (data->CursorPos < lastCaretPos) { - caretDirection = OrbisImeCaretMovementDirection::Left; - } else if (data->CursorPos > lastCaretPos) { - caretDirection = OrbisImeCaretMovementDirection::Right; - } - - OrbisImeEvent event{}; - event.id = OrbisImeEventId::UpdateCaret; - event.param.caret_move = caretDirection; - - lastCaretPos = data->CursorPos; - ui->state->SendEvent(&event); - } - - return 0; -} - -void ImeUi::Free() { - RemoveLayer(this); -} - -}; // namespace Libraries::Ime +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ime_ui.h" +#include "imgui/imgui_std.h" + +namespace Libraries::Ime { + +using namespace ImGui; + +static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; + +ImeState::ImeState(const OrbisImeParam* param) { + if (!param) { + return; + } + + work_buffer = param->work; + text_buffer = param->inputTextBuffer; + + std::size_t text_len = std::char_traits::length(text_buffer); + if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(), + ORBIS_IME_MAX_TEXT_LENGTH * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); + } +} + +ImeState::ImeState(ImeState&& other) noexcept + : work_buffer(other.work_buffer), text_buffer(other.text_buffer), + current_text(std::move(other.current_text)), event_queue(std::move(other.event_queue)) { + other.text_buffer = nullptr; +} + +ImeState& ImeState::operator=(ImeState&& other) noexcept { + if (this != &other) { + work_buffer = other.work_buffer; + text_buffer = other.text_buffer; + current_text = std::move(other.current_text); + event_queue = std::move(other.event_queue); + + other.text_buffer = nullptr; + } + return *this; +} + +void ImeState::SendEvent(OrbisImeEvent* event) { + std::unique_lock lock{queue_mutex}; + event_queue.push(*event); +} + +void ImeState::SendEnterEvent() { + OrbisImeEvent enterEvent{}; + enterEvent.id = OrbisImeEventId::PressEnter; + SendEvent(&enterEvent); +} + +void ImeState::SendCloseEvent() { + OrbisImeEvent closeEvent{}; + closeEvent.id = OrbisImeEventId::PressClose; + closeEvent.param.text.str = reinterpret_cast(work_buffer); + SendEvent(&closeEvent); +} + +void ImeState::SetText(const char16_t* text, u32 length) {} + +void ImeState::SetCaret(u32 position) {} + +bool ImeState::ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len, + char* utf8_text, std::size_t utf8_text_len) { + std::fill(utf8_text, utf8_text + utf8_text_len, '\0'); + const ImWchar* orbis_text_ptr = reinterpret_cast(orbis_text); + ImTextStrToUtf8(utf8_text, utf8_text_len, orbis_text_ptr, orbis_text_ptr + orbis_text_len); + + return true; +} + +bool ImeState::ConvertUTF8ToOrbis(const char* utf8_text, std::size_t utf8_text_len, + char16_t* orbis_text, std::size_t orbis_text_len) { + std::fill(orbis_text, orbis_text + orbis_text_len, u'\0'); + ImTextStrFromUtf8(reinterpret_cast(orbis_text), orbis_text_len, utf8_text, nullptr); + + return true; +} + +ImeUi::ImeUi(ImeState* state, const OrbisImeParam* param) : state(state), ime_param(param) { + if (param) { + AddLayer(this); + } +} + +ImeUi::~ImeUi() { + std::scoped_lock lock(draw_mutex); + Free(); +} + +ImeUi& ImeUi::operator=(ImeUi&& other) { + std::scoped_lock lock(draw_mutex, other.draw_mutex); + Free(); + + state = other.state; + ime_param = other.ime_param; + first_render = other.first_render; + other.state = nullptr; + other.ime_param = nullptr; + + AddLayer(this); + return *this; +} + +void ImeUi::Draw() { + std::unique_lock lock{draw_mutex}; + + if (!state) { + return; + } + + const auto& ctx = *GetCurrentContext(); + const auto& io = ctx.IO; + + // TODO: Figure out how to properly translate the positions - + // for example, if a game wants to center the IME panel, + // we have to translate the panel position in a way that it + // still becomes centered, as the game normally calculates + // the position assuming a it's running on a 1920x1080 screen, + // whereas we are running on a 1280x720 window size (by default). + // + // e.g. Panel position calculation from a game: + // param.posx = (1920 / 2) - (panelWidth / 2); + // param.posy = (1080 / 2) - (panelHeight / 2); + const auto size = GetIO().DisplaySize; + f32 pos_x = (ime_param->posx / 1920.0f * (float)size.x); + f32 pos_y = (ime_param->posy / 1080.0f * (float)size.y); + + ImVec2 window_pos = {pos_x, pos_y}; + ImVec2 window_size = {500.0f, 100.0f}; + + // SetNextWindowPos(window_pos); + SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), + ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + + if (first_render || !io.NavActive) { + SetNextWindowFocus(); + } + + if (Begin("IME##Ime", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoSavedSettings)) { + DrawPrettyBackground(); + + DrawInputText(); + SetCursorPosY(GetCursorPosY() + 10.0f); + + const char* button_text; + button_text = "Done##ImeDone"; + + float button_spacing = 10.0f; + float total_button_width = BUTTON_SIZE.x * 2 + button_spacing; + float button_start_pos = (window_size.x - total_button_width) / 2.0f; + + SetCursorPosX(button_start_pos); + + if (Button(button_text, BUTTON_SIZE) || (IsKeyPressed(ImGuiKey_Enter))) { + state->SendEnterEvent(); + } + + SameLine(0.0f, button_spacing); + + if (Button("Close##ImeClose", BUTTON_SIZE)) { + state->SendCloseEvent(); + } + } + End(); + + first_render = false; +} + +void ImeUi::DrawInputText() { + ImVec2 input_size = {GetWindowWidth() - 40.0f, 0.0f}; + SetCursorPosX(20.0f); + if (first_render) { + SetKeyboardFocusHere(); + } + if (InputTextEx("##ImeInput", nullptr, state->current_text.begin(), ime_param->maxTextLength, + input_size, ImGuiInputTextFlags_CallbackAlways, InputTextCallback, this)) { + } +} + +int ImeUi::InputTextCallback(ImGuiInputTextCallbackData* data) { + ImeUi* ui = static_cast(data->UserData); + ASSERT(ui); + + static std::string lastText; + std::string currentText(data->Buf, data->BufTextLen); + if (currentText != lastText) { + OrbisImeEditText eventParam{}; + eventParam.str = reinterpret_cast(ui->ime_param->work); + eventParam.caret_index = data->CursorPos; + eventParam.area_num = 1; + + eventParam.text_area[0].mode = 1; // Edit mode + eventParam.text_area[0].index = data->CursorPos; + eventParam.text_area[0].length = data->BufTextLen; + + if (!ui->state->ConvertUTF8ToOrbis(data->Buf, data->BufTextLen, eventParam.str, + ui->ime_param->maxTextLength)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert Orbis char to UTF-8"); + return 0; + } + + if (!ui->state->ConvertUTF8ToOrbis(data->Buf, data->BufTextLen, + ui->ime_param->inputTextBuffer, + ui->ime_param->maxTextLength)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert Orbis char to UTF-8"); + return 0; + } + + OrbisImeEvent event{}; + event.id = OrbisImeEventId::UpdateText; + event.param.text = eventParam; + + lastText = currentText; + ui->state->SendEvent(&event); + } + + static int lastCaretPos = -1; + if (lastCaretPos == -1) { + lastCaretPos = data->CursorPos; + } else if (data->CursorPos != lastCaretPos) { + OrbisImeCaretMovementDirection caretDirection = OrbisImeCaretMovementDirection::Still; + if (data->CursorPos < lastCaretPos) { + caretDirection = OrbisImeCaretMovementDirection::Left; + } else if (data->CursorPos > lastCaretPos) { + caretDirection = OrbisImeCaretMovementDirection::Right; + } + + OrbisImeEvent event{}; + event.id = OrbisImeEventId::UpdateCaret; + event.param.caret_move = caretDirection; + + lastCaretPos = data->CursorPos; + ui->state->SendEvent(&event); + } + + return 0; +} + +void ImeUi::Free() { + RemoveLayer(this); +} + +}; // namespace Libraries::Ime diff --git a/src/core/libraries/ime/ime_ui.h b/src/core/libraries/ime/ime_ui.h index a2a806bb9..3eea22b8c 100644 --- a/src/core/libraries/ime/ime_ui.h +++ b/src/core/libraries/ime/ime_ui.h @@ -1,76 +1,76 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include "imgui/imgui_layer.h" - -#include "common/cstring.h" -#include "common/types.h" - -#include "ime.h" - -namespace Libraries::Ime { - -class ImeHandler; -class ImeUi; - -class ImeState { - friend class ImeHandler; - friend class ImeUi; - - void* work_buffer{}; - char16_t* text_buffer{}; - - // A character can hold up to 4 bytes in UTF-8 - Common::CString current_text; - - std::queue event_queue; - std::mutex queue_mutex; - -public: - ImeState(const OrbisImeParam* param = nullptr); - ImeState(ImeState&& other) noexcept; - ImeState& operator=(ImeState&& other) noexcept; - - void SendEvent(OrbisImeEvent* event); - void SendEnterEvent(); - void SendCloseEvent(); - - void SetText(const char16_t* text, u32 length); - void SetCaret(u32 position); - -private: - bool ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len, char* utf8_text, - std::size_t native_text_len); - bool ConvertUTF8ToOrbis(const char* native_text, std::size_t utf8_text_len, - char16_t* orbis_text, std::size_t orbis_text_len); -}; - -class ImeUi : public ImGui::Layer { - ImeState* state{}; - const OrbisImeParam* ime_param{}; - - bool first_render = true; - std::mutex draw_mutex; - -public: - explicit ImeUi(ImeState* state = nullptr, const OrbisImeParam* param = nullptr); - ~ImeUi() override; - ImeUi(const ImeUi& other) = delete; - ImeUi& operator=(ImeUi&& other); - - void Draw() override; - -private: - void Free(); - - void DrawInputText(); - - static int InputTextCallback(ImGuiInputTextCallbackData* data); -}; - +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "imgui/imgui_layer.h" + +#include "common/cstring.h" +#include "common/types.h" + +#include "ime.h" + +namespace Libraries::Ime { + +class ImeHandler; +class ImeUi; + +class ImeState { + friend class ImeHandler; + friend class ImeUi; + + void* work_buffer{}; + char16_t* text_buffer{}; + + // A character can hold up to 4 bytes in UTF-8 + Common::CString current_text; + + std::queue event_queue; + std::mutex queue_mutex; + +public: + ImeState(const OrbisImeParam* param = nullptr); + ImeState(ImeState&& other) noexcept; + ImeState& operator=(ImeState&& other) noexcept; + + void SendEvent(OrbisImeEvent* event); + void SendEnterEvent(); + void SendCloseEvent(); + + void SetText(const char16_t* text, u32 length); + void SetCaret(u32 position); + +private: + bool ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len, char* utf8_text, + std::size_t native_text_len); + bool ConvertUTF8ToOrbis(const char* native_text, std::size_t utf8_text_len, + char16_t* orbis_text, std::size_t orbis_text_len); +}; + +class ImeUi : public ImGui::Layer { + ImeState* state{}; + const OrbisImeParam* ime_param{}; + + bool first_render = true; + std::mutex draw_mutex; + +public: + explicit ImeUi(ImeState* state = nullptr, const OrbisImeParam* param = nullptr); + ~ImeUi() override; + ImeUi(const ImeUi& other) = delete; + ImeUi& operator=(ImeUi&& other); + + void Draw() override; + +private: + void Free(); + + void DrawInputText(); + + static int InputTextCallback(ImGuiInputTextCallbackData* data); +}; + }; // namespace Libraries::Ime \ No newline at end of file diff --git a/src/core/libraries/kernel/equeue.cpp b/src/core/libraries/kernel/equeue.cpp index 6543cc319..42a8eed89 100644 --- a/src/core/libraries/kernel/equeue.cpp +++ b/src/core/libraries/kernel/equeue.cpp @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include "common/assert.h" #include "common/debug.h" #include "common/logging/log.h" diff --git a/src/core/libraries/kernel/sync/mutex.cpp b/src/core/libraries/kernel/sync/mutex.cpp index c5e3eba1d..b9bb33036 100644 --- a/src/core/libraries/kernel/sync/mutex.cpp +++ b/src/core/libraries/kernel/sync/mutex.cpp @@ -1,52 +1,52 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "mutex.h" - -#include "common/assert.h" - -namespace Libraries::Kernel { - -TimedMutex::TimedMutex() { -#ifdef _WIN64 - mtx = CreateMutex(nullptr, false, nullptr); - ASSERT(mtx); -#endif -} - -TimedMutex::~TimedMutex() { -#ifdef _WIN64 - CloseHandle(mtx); -#endif -} - -void TimedMutex::lock() { -#ifdef _WIN64 - for (;;) { - u64 res = WaitForSingleObjectEx(mtx, INFINITE, true); - if (res == WAIT_OBJECT_0) { - return; - } - } -#else - mtx.lock(); -#endif -} - -bool TimedMutex::try_lock() { -#ifdef _WIN64 - return WaitForSingleObjectEx(mtx, 0, true) == WAIT_OBJECT_0; -#else - return mtx.try_lock(); -#endif -} - -void TimedMutex::unlock() { -#ifdef _WIN64 - ReleaseMutex(mtx); -#else - mtx.unlock(); -#endif -} - +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "mutex.h" + +#include "common/assert.h" + +namespace Libraries::Kernel { + +TimedMutex::TimedMutex() { +#ifdef _WIN64 + mtx = CreateMutex(nullptr, false, nullptr); + ASSERT(mtx); +#endif +} + +TimedMutex::~TimedMutex() { +#ifdef _WIN64 + CloseHandle(mtx); +#endif +} + +void TimedMutex::lock() { +#ifdef _WIN64 + for (;;) { + u64 res = WaitForSingleObjectEx(mtx, INFINITE, true); + if (res == WAIT_OBJECT_0) { + return; + } + } +#else + mtx.lock(); +#endif +} + +bool TimedMutex::try_lock() { +#ifdef _WIN64 + return WaitForSingleObjectEx(mtx, 0, true) == WAIT_OBJECT_0; +#else + return mtx.try_lock(); +#endif +} + +void TimedMutex::unlock() { +#ifdef _WIN64 + ReleaseMutex(mtx); +#else + mtx.unlock(); +#endif +} + } // namespace Libraries::Kernel \ No newline at end of file diff --git a/src/core/libraries/kernel/sync/mutex.h b/src/core/libraries/kernel/sync/mutex.h index f14a920b4..d26c984ef 100644 --- a/src/core/libraries/kernel/sync/mutex.h +++ b/src/core/libraries/kernel/sync/mutex.h @@ -1,80 +1,80 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "common/types.h" - -#ifdef _WIN64 -#include -#else -#include -#endif - -namespace Libraries::Kernel { - -class TimedMutex { -public: - TimedMutex(); - ~TimedMutex(); - - void lock(); - bool try_lock(); - - void unlock(); - - template - bool try_lock_for(const std::chrono::duration& rel_time) { -#ifdef _WIN64 - constexpr auto zero = std::chrono::duration::zero(); - const auto now = std::chrono::steady_clock::now(); - - std::chrono::steady_clock::time_point abs_time = now; - if (rel_time > zero) { - constexpr auto max = (std::chrono::steady_clock::time_point::max)(); - if (abs_time < max - rel_time) { - abs_time += rel_time; - } else { - abs_time = max; - } - } - - return try_lock_until(abs_time); -#else - return mtx.try_lock_for(rel_time); -#endif - } - - template - bool try_lock_until(const std::chrono::time_point& abs_time) { -#ifdef _WIN64 - for (;;) { - const auto now = Clock::now(); - if (abs_time <= now) { - return false; - } - - const auto rel_ms = std::chrono::ceil(abs_time - now); - u64 res = WaitForSingleObjectEx(mtx, static_cast(rel_ms.count()), true); - if (res == WAIT_OBJECT_0) { - return true; - } else if (res == WAIT_TIMEOUT) { - return false; - } - } -#else - return mtx.try_lock_until(abs_time); -#endif - } - -private: -#ifdef _WIN64 - HANDLE mtx; -#else - std::timed_mutex mtx; -#endif -}; - +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/types.h" + +#ifdef _WIN64 +#include +#else +#include +#endif + +namespace Libraries::Kernel { + +class TimedMutex { +public: + TimedMutex(); + ~TimedMutex(); + + void lock(); + bool try_lock(); + + void unlock(); + + template + bool try_lock_for(const std::chrono::duration& rel_time) { +#ifdef _WIN64 + constexpr auto zero = std::chrono::duration::zero(); + const auto now = std::chrono::steady_clock::now(); + + std::chrono::steady_clock::time_point abs_time = now; + if (rel_time > zero) { + constexpr auto max = (std::chrono::steady_clock::time_point::max)(); + if (abs_time < max - rel_time) { + abs_time += rel_time; + } else { + abs_time = max; + } + } + + return try_lock_until(abs_time); +#else + return mtx.try_lock_for(rel_time); +#endif + } + + template + bool try_lock_until(const std::chrono::time_point& abs_time) { +#ifdef _WIN64 + for (;;) { + const auto now = Clock::now(); + if (abs_time <= now) { + return false; + } + + const auto rel_ms = std::chrono::ceil(abs_time - now); + u64 res = WaitForSingleObjectEx(mtx, static_cast(rel_ms.count()), true); + if (res == WAIT_OBJECT_0) { + return true; + } else if (res == WAIT_TIMEOUT) { + return false; + } + } +#else + return mtx.try_lock_until(abs_time); +#endif + } + +private: +#ifdef _WIN64 + HANDLE mtx; +#else + std::timed_mutex mtx; +#endif +}; + } // namespace Libraries::Kernel \ No newline at end of file diff --git a/src/core/libraries/kernel/sync/semaphore.h b/src/core/libraries/kernel/sync/semaphore.h index 48a5dc0d8..738df37d8 100644 --- a/src/core/libraries/kernel/sync/semaphore.h +++ b/src/core/libraries/kernel/sync/semaphore.h @@ -1,167 +1,164 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include "common/assert.h" -#include "common/types.h" - -#ifdef _WIN64 -#include -#elif defined(__APPLE__) -#include -#else -#include -#endif - -namespace Libraries::Kernel { - -template -class Semaphore { -public: - Semaphore(s32 initialCount) -#if !defined(_WIN64) && !defined(__APPLE__) - : sem{initialCount} -#endif - { -#ifdef _WIN64 - sem = CreateSemaphore(nullptr, initialCount, max, nullptr); - ASSERT(sem); -#elif defined(__APPLE__) - sem = dispatch_semaphore_create(initialCount); - ASSERT(sem); -#endif - } - - ~Semaphore() { -#ifdef _WIN64 - CloseHandle(sem); -#elif defined(__APPLE__) - dispatch_release(sem); -#endif - } - - void release() { -#ifdef _WIN64 - ReleaseSemaphore(sem, 1, nullptr); -#elif defined(__APPLE__) - dispatch_semaphore_signal(sem); -#else - sem.release(); -#endif - } - - void acquire() { -#ifdef _WIN64 - for (;;) { - u64 res = WaitForSingleObjectEx(sem, INFINITE, true); - if (res == WAIT_OBJECT_0) { - return; - } - } -#elif defined(__APPLE__) - for (;;) { - const auto res = dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); - if (res == 0) { - return; - } - } -#else - sem.acquire(); -#endif - } - - bool try_acquire() { -#ifdef _WIN64 - return WaitForSingleObjectEx(sem, 0, true) == WAIT_OBJECT_0; -#elif defined(__APPLE__) - return dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW) == 0; -#else - return sem.try_acquire(); -#endif - } - - template - bool try_acquire_for(const std::chrono::duration& rel_time) { -#ifdef _WIN64 - const auto start_time = std::chrono::high_resolution_clock::now(); - auto rel_time_ms = std::chrono::ceil(rel_time); - - while (rel_time_ms.count() > 0) { - u64 timeout_ms = static_cast(rel_time_ms.count()); - u64 res = WaitForSingleObjectEx(sem, timeout_ms, true); - if (res == WAIT_OBJECT_0) { - return true; - } else if (res == WAIT_IO_COMPLETION) { - auto elapsed_time = std::chrono::high_resolution_clock::now() - start_time; - rel_time_ms -= std::chrono::duration_cast(elapsed_time); - } else { - return false; - } - } - - return false; -#elif defined(__APPLE__) - const auto rel_time_ns = std::chrono::ceil(rel_time).count(); - const auto timeout = dispatch_time(DISPATCH_TIME_NOW, rel_time_ns); - return dispatch_semaphore_wait(sem, timeout) == 0; -#else - return sem.try_acquire_for(rel_time); -#endif - } - - template - bool try_acquire_until(const std::chrono::time_point& abs_time) { -#ifdef _WIN64 - const auto start_time = Clock::now(); - if (start_time >= abs_time) { - return false; - } - - auto rel_time = std::chrono::ceil(abs_time - start_time); - while (rel_time.count() > 0) { - u64 timeout_ms = static_cast(rel_time.count()); - u64 res = WaitForSingleObjectEx(sem, timeout_ms, true); - if (res == WAIT_OBJECT_0) { - return true; - } else if (res == WAIT_IO_COMPLETION) { - auto elapsed_time = Clock::now() - start_time; - rel_time -= std::chrono::duration_cast(elapsed_time); - } else { - return false; - } - } - - return false; -#elif defined(__APPLE__) - auto abs_s = std::chrono::time_point_cast(abs_time); - auto abs_ns = std::chrono::time_point_cast(abs_time) - - std::chrono::time_point_cast(abs_s); - const timespec abs_timespec = { - .tv_sec = abs_s.time_since_epoch().count(), - .tv_nsec = abs_ns.count(), - }; - const auto timeout = dispatch_walltime(&abs_timespec, 0); - return dispatch_semaphore_wait(sem, timeout) == 0; -#else - return sem.try_acquire_until(abs_time); -#endif - } - -private: -#ifdef _WIN64 - HANDLE sem; -#elif defined(__APPLE__) - dispatch_semaphore_t sem; -#else - std::counting_semaphore sem; -#endif -}; - -using BinarySemaphore = Semaphore<1>; -using CountingSemaphore = Semaphore<0x7FFFFFFF /*ORBIS_KERNEL_SEM_VALUE_MAX*/>; - +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/assert.h" +#include "common/types.h" + +#ifdef _WIN64 +#include +#elif defined(__APPLE__) +#include +#else +#include +#endif + +namespace Libraries::Kernel { + +template +class Semaphore { +public: + Semaphore(s32 initialCount) +#if !defined(_WIN64) && !defined(__APPLE__) + : sem{initialCount} +#endif + { +#ifdef _WIN64 + sem = CreateSemaphore(nullptr, initialCount, max, nullptr); + ASSERT(sem); +#elif defined(__APPLE__) + sem = dispatch_semaphore_create(initialCount); + ASSERT(sem); +#endif + } + + ~Semaphore() { +#ifdef _WIN64 + CloseHandle(sem); +#elif defined(__APPLE__) + dispatch_release(sem); +#endif + } + + void release() { +#ifdef _WIN64 + ReleaseSemaphore(sem, 1, nullptr); +#elif defined(__APPLE__) + dispatch_semaphore_signal(sem); +#else + sem.release(); +#endif + } + + void acquire() { +#ifdef _WIN64 + for (;;) { + u64 res = WaitForSingleObjectEx(sem, INFINITE, true); + if (res == WAIT_OBJECT_0) { + return; + } + } +#elif defined(__APPLE__) + for (;;) { + const auto res = dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + if (res == 0) { + return; + } + } +#else + sem.acquire(); +#endif + } + + bool try_acquire() { +#ifdef _WIN64 + return WaitForSingleObjectEx(sem, 0, true) == WAIT_OBJECT_0; +#elif defined(__APPLE__) + return dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW) == 0; +#else + return sem.try_acquire(); +#endif + } + + template + bool try_acquire_for(const std::chrono::duration& rel_time) { +#ifdef _WIN64 + const auto start_time = std::chrono::high_resolution_clock::now(); + auto rel_time_ms = std::chrono::ceil(rel_time); + + do { + u64 timeout_ms = static_cast(rel_time_ms.count()); + u64 res = WaitForSingleObjectEx(sem, timeout_ms, true); + if (res == WAIT_OBJECT_0) { + return true; + } else if (res == WAIT_IO_COMPLETION) { + auto elapsed_time = std::chrono::high_resolution_clock::now() - start_time; + rel_time_ms -= std::chrono::duration_cast(elapsed_time); + } else { + return false; + } + } while (rel_time_ms.count() > 0); + + return false; +#elif defined(__APPLE__) + const auto rel_time_ns = std::chrono::ceil(rel_time).count(); + const auto timeout = dispatch_time(DISPATCH_TIME_NOW, rel_time_ns); + return dispatch_semaphore_wait(sem, timeout) == 0; +#else + return sem.try_acquire_for(rel_time); +#endif + } + + template + bool try_acquire_until(const std::chrono::time_point& abs_time) { +#ifdef _WIN64 + const auto start_time = Clock::now(); + + auto rel_time = std::chrono::ceil(abs_time - start_time); + do { + u64 timeout_ms = static_cast(rel_time.count()); + u64 res = WaitForSingleObjectEx(sem, timeout_ms, true); + if (res == WAIT_OBJECT_0) { + return true; + } else if (res == WAIT_IO_COMPLETION) { + auto elapsed_time = Clock::now() - start_time; + rel_time -= std::chrono::duration_cast(elapsed_time); + } else { + return false; + } + } while (rel_time.count() > 0); + + return false; +#elif defined(__APPLE__) + auto abs_s = std::chrono::time_point_cast(abs_time); + auto abs_ns = std::chrono::time_point_cast(abs_time) - + std::chrono::time_point_cast(abs_s); + const timespec abs_timespec = { + .tv_sec = abs_s.time_since_epoch().count(), + .tv_nsec = abs_ns.count(), + }; + const auto timeout = dispatch_walltime(&abs_timespec, 0); + return dispatch_semaphore_wait(sem, timeout) == 0; +#else + return sem.try_acquire_until(abs_time); +#endif + } + +private: +#ifdef _WIN64 + HANDLE sem; +#elif defined(__APPLE__) + dispatch_semaphore_t sem; +#else + std::counting_semaphore sem; +#endif +}; + +using BinarySemaphore = Semaphore<1>; +using CountingSemaphore = Semaphore<0x7FFFFFFF /*ORBIS_KERNEL_SEM_VALUE_MAX*/>; + } // namespace Libraries::Kernel \ No newline at end of file diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index 017984e0d..cc391e928 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.cpp @@ -22,7 +22,7 @@ void SigactionHandler(int signum, siginfo_t* inf, ucontext_t* raw_context) { if (handler) { auto ctx = Ucontext{}; #ifdef __APPLE__ - auto& regs = raw_context->uc_mcontext->__ss; + const auto& regs = raw_context->uc_mcontext->__ss; ctx.uc_mcontext.mc_r8 = regs.__r8; ctx.uc_mcontext.mc_r9 = regs.__r9; ctx.uc_mcontext.mc_r10 = regs.__r10; @@ -42,7 +42,7 @@ void SigactionHandler(int signum, siginfo_t* inf, ucontext_t* raw_context) { ctx.uc_mcontext.mc_fs = regs.__fs; ctx.uc_mcontext.mc_gs = regs.__gs; #else - auto& regs = raw_context->uc_mcontext.gregs; + const auto& regs = raw_context->uc_mcontext.gregs; ctx.uc_mcontext.mc_r8 = regs[REG_R8]; ctx.uc_mcontext.mc_r9 = regs[REG_R9]; ctx.uc_mcontext.mc_r10 = regs[REG_R10]; @@ -131,8 +131,12 @@ int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) { LOG_WARNING(Lib_Kernel, "Raising exception on thread '{}'", thread->name); ASSERT_MSG(signum == POSIX_SIGUSR1, "Attempting to raise non user defined signal!"); #ifndef _WIN64 - pthread_t pthr = *reinterpret_cast(thread->native_thr.GetHandle()); - pthread_kill(pthr, SIGUSR2); + const auto pthr = reinterpret_cast(thread->native_thr.GetHandle()); + const auto ret = pthread_kill(pthr, SIGUSR2); + if (ret != 0) { + LOG_ERROR(Kernel, "Failed to send exception signal to thread '{}': {}", thread->name, + strerror(ret)); + } #else USER_APC_OPTION option; option.UserApcFlags = QueueUserApcFlagsSpecialUserApc; diff --git a/src/core/libraries/kernel/threads/tcb.cpp b/src/core/libraries/kernel/threads/tcb.cpp index e5a158216..e3e8b90c3 100644 --- a/src/core/libraries/kernel/threads/tcb.cpp +++ b/src/core/libraries/kernel/threads/tcb.cpp @@ -53,6 +53,7 @@ Core::Tcb* TcbCtor(Pthread* thread, int initial) { if (tcb) { tcb->tcb_thread = thread; + tcb->tcb_fiber = nullptr; } return tcb; } diff --git a/src/core/libraries/videodec/videodec2.cpp b/src/core/libraries/videodec/videodec2.cpp index 0bd68afbd..07f7a274b 100644 --- a/src/core/libraries/videodec/videodec2.cpp +++ b/src/core/libraries/videodec/videodec2.cpp @@ -1,199 +1,199 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/libraries/libs.h" -#include "core/libraries/videodec/videodec2.h" -#include "core/libraries/videodec/videodec2_impl.h" -#include "core/libraries/videodec/videodec_error.h" - -namespace Libraries::Vdec2 { - -static constexpr u64 kMinimumMemorySize = 32_MB; ///> Fake minimum memory size for querying - -s32 PS4_SYSV_ABI -sceVideodec2QueryComputeMemoryInfo(OrbisVideodec2ComputeMemoryInfo* computeMemInfo) { - LOG_INFO(Lib_Vdec2, "called"); - - if (!computeMemInfo) { - return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; - } - if (computeMemInfo->thisSize != sizeof(OrbisVideodec2ComputeMemoryInfo)) { - return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; - } - - computeMemInfo->cpuGpuMemory = nullptr; - computeMemInfo->cpuGpuMemorySize = kMinimumMemorySize; - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI -sceVideodec2AllocateComputeQueue(const OrbisVideodec2ComputeConfigInfo* computeCfgInfo, - const OrbisVideodec2ComputeMemoryInfo* computeMemInfo, - OrbisVideodec2ComputeQueue* computeQueue) { - LOG_INFO(Lib_Vdec2, "called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceVideodec2ReleaseComputeQueue(OrbisVideodec2ComputeQueue computeQueue) { - LOG_INFO(Lib_Vdec2, "called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI -sceVideodec2QueryDecoderMemoryInfo(const OrbisVideodec2DecoderConfigInfo* decoderCfgInfo, - OrbisVideodec2DecoderMemoryInfo* decoderMemInfo) { - LOG_INFO(Lib_Vdec2, "called"); - - if (!decoderCfgInfo || !decoderMemInfo) { - return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; - } - if (decoderCfgInfo->thisSize != sizeof(OrbisVideodec2DecoderConfigInfo) || - decoderMemInfo->thisSize != sizeof(OrbisVideodec2DecoderMemoryInfo)) { - return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; - } - - decoderMemInfo->cpuMemory = nullptr; - decoderMemInfo->gpuMemory = nullptr; - decoderMemInfo->cpuGpuMemory = nullptr; - - decoderMemInfo->cpuGpuMemorySize = kMinimumMemorySize; - decoderMemInfo->cpuMemorySize = kMinimumMemorySize; - decoderMemInfo->gpuMemorySize = kMinimumMemorySize; - - decoderMemInfo->maxFrameBufferSize = kMinimumMemorySize; - decoderMemInfo->frameBufferAlignment = 0x100; - - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceVideodec2CreateDecoder(const OrbisVideodec2DecoderConfigInfo* decoderCfgInfo, - const OrbisVideodec2DecoderMemoryInfo* decoderMemInfo, - OrbisVideodec2Decoder* decoder) { - LOG_INFO(Lib_Vdec2, "called"); - - if (!decoderCfgInfo || !decoderMemInfo || !decoder) { - return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; - } - if (decoderCfgInfo->thisSize != sizeof(OrbisVideodec2DecoderConfigInfo) || - decoderMemInfo->thisSize != sizeof(OrbisVideodec2DecoderMemoryInfo)) { - return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; - } - - *decoder = new VdecDecoder(*decoderCfgInfo, *decoderMemInfo); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceVideodec2DeleteDecoder(OrbisVideodec2Decoder decoder) { - LOG_INFO(Lib_Vdec2, "called"); - - if (!decoder) { - return ORBIS_VIDEODEC2_ERROR_DECODER_INSTANCE; - } - - delete decoder; - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceVideodec2Decode(OrbisVideodec2Decoder decoder, - const OrbisVideodec2InputData* inputData, - OrbisVideodec2FrameBuffer* frameBuffer, - OrbisVideodec2OutputInfo* outputInfo) { - LOG_TRACE(Lib_Vdec2, "called"); - - if (!decoder) { - return ORBIS_VIDEODEC2_ERROR_DECODER_INSTANCE; - } - if (!inputData || !frameBuffer || !outputInfo) { - return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; - } - if (inputData->thisSize != sizeof(OrbisVideodec2InputData) || - frameBuffer->thisSize != sizeof(OrbisVideodec2FrameBuffer)) { - return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; - } - - return decoder->Decode(*inputData, *frameBuffer, *outputInfo); -} - -s32 PS4_SYSV_ABI sceVideodec2Flush(OrbisVideodec2Decoder decoder, - OrbisVideodec2FrameBuffer* frameBuffer, - OrbisVideodec2OutputInfo* outputInfo) { - LOG_INFO(Lib_Vdec2, "called"); - - if (!decoder) { - return ORBIS_VIDEODEC2_ERROR_DECODER_INSTANCE; - } - if (!frameBuffer || !outputInfo) { - return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; - } - if (frameBuffer->thisSize != sizeof(OrbisVideodec2FrameBuffer) || - outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) { - return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; - } - - return decoder->Flush(*frameBuffer, *outputInfo); -} - -s32 PS4_SYSV_ABI sceVideodec2Reset(OrbisVideodec2Decoder decoder) { - LOG_INFO(Lib_Vdec2, "called"); - - if (!decoder) { - return ORBIS_VIDEODEC2_ERROR_DECODER_INSTANCE; - } - - return decoder->Reset(); -} - -s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outputInfo, - void* p1stPictureInfoOut, void* p2ndPictureInfoOut) { - LOG_TRACE(Lib_Vdec2, "called"); - - if (!outputInfo) { - return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; - } - if (outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) { - return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; - } - if (outputInfo->pictureCount == 0 || gPictureInfos.empty()) { - return ORBIS_OK; - } - - if (p1stPictureInfoOut) { - OrbisVideodec2AvcPictureInfo* picInfo = - static_cast(p1stPictureInfoOut); - if (picInfo->thisSize != sizeof(OrbisVideodec2AvcPictureInfo)) { - return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; - } - *picInfo = gPictureInfos.back(); - } - - if (outputInfo->pictureCount > 1) { - UNREACHABLE(); - } - - return ORBIS_OK; -} - -void RegisterlibSceVdec2(Core::Loader::SymbolsResolver* sym) { - LIB_FUNCTION("RnDibcGCPKw", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, - sceVideodec2QueryComputeMemoryInfo); - LIB_FUNCTION("eD+X2SmxUt4", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, - sceVideodec2AllocateComputeQueue); - LIB_FUNCTION("UvtA3FAiF4Y", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, - sceVideodec2ReleaseComputeQueue); - - LIB_FUNCTION("qqMCwlULR+E", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, - sceVideodec2QueryDecoderMemoryInfo); - LIB_FUNCTION("CNNRoRYd8XI", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, - sceVideodec2CreateDecoder); - LIB_FUNCTION("jwImxXRGSKA", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, - sceVideodec2DeleteDecoder); - LIB_FUNCTION("852F5+q6+iM", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, sceVideodec2Decode); - LIB_FUNCTION("l1hXwscLuCY", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, sceVideodec2Flush); - LIB_FUNCTION("wJXikG6QFN8", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, sceVideodec2Reset); - LIB_FUNCTION("NtXRa3dRzU0", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, - sceVideodec2GetPictureInfo); -} - -} // namespace Libraries::Vdec2 +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/libraries/libs.h" +#include "core/libraries/videodec/videodec2.h" +#include "core/libraries/videodec/videodec2_impl.h" +#include "core/libraries/videodec/videodec_error.h" + +namespace Libraries::Vdec2 { + +static constexpr u64 kMinimumMemorySize = 32_MB; ///> Fake minimum memory size for querying + +s32 PS4_SYSV_ABI +sceVideodec2QueryComputeMemoryInfo(OrbisVideodec2ComputeMemoryInfo* computeMemInfo) { + LOG_INFO(Lib_Vdec2, "called"); + + if (!computeMemInfo) { + return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; + } + if (computeMemInfo->thisSize != sizeof(OrbisVideodec2ComputeMemoryInfo)) { + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + + computeMemInfo->cpuGpuMemory = nullptr; + computeMemInfo->cpuGpuMemorySize = kMinimumMemorySize; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI +sceVideodec2AllocateComputeQueue(const OrbisVideodec2ComputeConfigInfo* computeCfgInfo, + const OrbisVideodec2ComputeMemoryInfo* computeMemInfo, + OrbisVideodec2ComputeQueue* computeQueue) { + LOG_INFO(Lib_Vdec2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceVideodec2ReleaseComputeQueue(OrbisVideodec2ComputeQueue computeQueue) { + LOG_INFO(Lib_Vdec2, "called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI +sceVideodec2QueryDecoderMemoryInfo(const OrbisVideodec2DecoderConfigInfo* decoderCfgInfo, + OrbisVideodec2DecoderMemoryInfo* decoderMemInfo) { + LOG_INFO(Lib_Vdec2, "called"); + + if (!decoderCfgInfo || !decoderMemInfo) { + return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; + } + if (decoderCfgInfo->thisSize != sizeof(OrbisVideodec2DecoderConfigInfo) || + decoderMemInfo->thisSize != sizeof(OrbisVideodec2DecoderMemoryInfo)) { + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + + decoderMemInfo->cpuMemory = nullptr; + decoderMemInfo->gpuMemory = nullptr; + decoderMemInfo->cpuGpuMemory = nullptr; + + decoderMemInfo->cpuGpuMemorySize = kMinimumMemorySize; + decoderMemInfo->cpuMemorySize = kMinimumMemorySize; + decoderMemInfo->gpuMemorySize = kMinimumMemorySize; + + decoderMemInfo->maxFrameBufferSize = kMinimumMemorySize; + decoderMemInfo->frameBufferAlignment = 0x100; + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceVideodec2CreateDecoder(const OrbisVideodec2DecoderConfigInfo* decoderCfgInfo, + const OrbisVideodec2DecoderMemoryInfo* decoderMemInfo, + OrbisVideodec2Decoder* decoder) { + LOG_INFO(Lib_Vdec2, "called"); + + if (!decoderCfgInfo || !decoderMemInfo || !decoder) { + return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; + } + if (decoderCfgInfo->thisSize != sizeof(OrbisVideodec2DecoderConfigInfo) || + decoderMemInfo->thisSize != sizeof(OrbisVideodec2DecoderMemoryInfo)) { + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + + *decoder = new VdecDecoder(*decoderCfgInfo, *decoderMemInfo); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceVideodec2DeleteDecoder(OrbisVideodec2Decoder decoder) { + LOG_INFO(Lib_Vdec2, "called"); + + if (!decoder) { + return ORBIS_VIDEODEC2_ERROR_DECODER_INSTANCE; + } + + delete decoder; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceVideodec2Decode(OrbisVideodec2Decoder decoder, + const OrbisVideodec2InputData* inputData, + OrbisVideodec2FrameBuffer* frameBuffer, + OrbisVideodec2OutputInfo* outputInfo) { + LOG_TRACE(Lib_Vdec2, "called"); + + if (!decoder) { + return ORBIS_VIDEODEC2_ERROR_DECODER_INSTANCE; + } + if (!inputData || !frameBuffer || !outputInfo) { + return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; + } + if (inputData->thisSize != sizeof(OrbisVideodec2InputData) || + frameBuffer->thisSize != sizeof(OrbisVideodec2FrameBuffer)) { + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + + return decoder->Decode(*inputData, *frameBuffer, *outputInfo); +} + +s32 PS4_SYSV_ABI sceVideodec2Flush(OrbisVideodec2Decoder decoder, + OrbisVideodec2FrameBuffer* frameBuffer, + OrbisVideodec2OutputInfo* outputInfo) { + LOG_INFO(Lib_Vdec2, "called"); + + if (!decoder) { + return ORBIS_VIDEODEC2_ERROR_DECODER_INSTANCE; + } + if (!frameBuffer || !outputInfo) { + return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; + } + if (frameBuffer->thisSize != sizeof(OrbisVideodec2FrameBuffer) || + outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) { + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + + return decoder->Flush(*frameBuffer, *outputInfo); +} + +s32 PS4_SYSV_ABI sceVideodec2Reset(OrbisVideodec2Decoder decoder) { + LOG_INFO(Lib_Vdec2, "called"); + + if (!decoder) { + return ORBIS_VIDEODEC2_ERROR_DECODER_INSTANCE; + } + + return decoder->Reset(); +} + +s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outputInfo, + void* p1stPictureInfoOut, void* p2ndPictureInfoOut) { + LOG_TRACE(Lib_Vdec2, "called"); + + if (!outputInfo) { + return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; + } + if (outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) { + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + if (outputInfo->pictureCount == 0 || gPictureInfos.empty()) { + return ORBIS_OK; + } + + if (p1stPictureInfoOut) { + OrbisVideodec2AvcPictureInfo* picInfo = + static_cast(p1stPictureInfoOut); + if (picInfo->thisSize != sizeof(OrbisVideodec2AvcPictureInfo)) { + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + *picInfo = gPictureInfos.back(); + } + + if (outputInfo->pictureCount > 1) { + UNREACHABLE(); + } + + return ORBIS_OK; +} + +void RegisterlibSceVdec2(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("RnDibcGCPKw", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, + sceVideodec2QueryComputeMemoryInfo); + LIB_FUNCTION("eD+X2SmxUt4", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, + sceVideodec2AllocateComputeQueue); + LIB_FUNCTION("UvtA3FAiF4Y", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, + sceVideodec2ReleaseComputeQueue); + + LIB_FUNCTION("qqMCwlULR+E", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, + sceVideodec2QueryDecoderMemoryInfo); + LIB_FUNCTION("CNNRoRYd8XI", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, + sceVideodec2CreateDecoder); + LIB_FUNCTION("jwImxXRGSKA", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, + sceVideodec2DeleteDecoder); + LIB_FUNCTION("852F5+q6+iM", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, sceVideodec2Decode); + LIB_FUNCTION("l1hXwscLuCY", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, sceVideodec2Flush); + LIB_FUNCTION("wJXikG6QFN8", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, sceVideodec2Reset); + LIB_FUNCTION("NtXRa3dRzU0", "libSceVideodec2", 1, "libSceVideodec2", 1, 1, + sceVideodec2GetPictureInfo); +} + +} // namespace Libraries::Vdec2 diff --git a/src/core/libraries/videodec/videodec2.h b/src/core/libraries/videodec/videodec2.h index 4617e1c20..abc8f8ab5 100644 --- a/src/core/libraries/videodec/videodec2.h +++ b/src/core/libraries/videodec/videodec2.h @@ -1,139 +1,139 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" - -#include "videodec2_avc.h" - -namespace Core::Loader { -class SymbolsResolver; -} -namespace Libraries::Vdec2 { - -class VdecDecoder; - -using OrbisVideodec2Decoder = VdecDecoder*; -using OrbisVideodec2ComputeQueue = void*; - -struct OrbisVideodec2DecoderConfigInfo { - u64 thisSize; - u32 resourceType; - u32 codecType; - u32 profile; - u32 maxLevel; - s32 maxFrameWidth; - s32 maxFrameHeight; - s32 maxDpbFrameCount; - u32 decodePipelineDepth; - OrbisVideodec2ComputeQueue computeQueue; - u64 cpuAffinityMask; - s32 cpuThreadPriority; - bool optimizeProgressiveVideo; - bool checkMemoryType; - u8 reserved0; - u8 reserved1; - void* extraConfigInfo; -}; -static_assert(sizeof(OrbisVideodec2DecoderConfigInfo) == 0x48); - -struct OrbisVideodec2DecoderMemoryInfo { - u64 thisSize; - u64 cpuMemorySize; - void* cpuMemory; - u64 gpuMemorySize; - void* gpuMemory; - u64 cpuGpuMemorySize; - void* cpuGpuMemory; - u64 maxFrameBufferSize; - u32 frameBufferAlignment; - u32 reserved0; -}; -static_assert(sizeof(OrbisVideodec2DecoderMemoryInfo) == 0x48); - -struct OrbisVideodec2InputData { - u64 thisSize; - void* auData; - u64 auSize; - u64 ptsData; - u64 dtsData; - u64 attachedData; -}; -static_assert(sizeof(OrbisVideodec2InputData) == 0x30); - -struct OrbisVideodec2OutputInfo { - u64 thisSize; - bool isValid; - bool isErrorFrame; - u8 pictureCount; - u32 codecType; - u32 frameWidth; - u32 framePitch; - u32 frameHeight; - void* frameBuffer; - u64 frameBufferSize; -}; -static_assert(sizeof(OrbisVideodec2OutputInfo) == 0x30); - -struct OrbisVideodec2FrameBuffer { - u64 thisSize; - void* frameBuffer; - u64 frameBufferSize; - bool isAccepted; -}; -static_assert(sizeof(OrbisVideodec2FrameBuffer) == 0x20); - -struct OrbisVideodec2ComputeMemoryInfo { - u64 thisSize; - u64 cpuGpuMemorySize; - void* cpuGpuMemory; -}; -static_assert(sizeof(OrbisVideodec2ComputeMemoryInfo) == 0x18); - -struct OrbisVideodec2ComputeConfigInfo { - u64 thisSize; - u16 computePipeId; - u16 computeQueueId; - bool checkMemoryType; - u8 reserved0; - u16 reserved1; -}; -static_assert(sizeof(OrbisVideodec2ComputeConfigInfo) == 0x10); - -s32 PS4_SYSV_ABI -sceVideodec2QueryComputeMemoryInfo(OrbisVideodec2ComputeMemoryInfo* computeMemInfo); - -s32 PS4_SYSV_ABI -sceVideodec2AllocateComputeQueue(const OrbisVideodec2ComputeConfigInfo* computeCfgInfo, - const OrbisVideodec2ComputeMemoryInfo* computeMemInfo, - OrbisVideodec2ComputeQueue* computeQueue); - -s32 PS4_SYSV_ABI sceVideodec2ReleaseComputeQueue(OrbisVideodec2ComputeQueue computeQueue); - -s32 PS4_SYSV_ABI -sceVideodec2QueryDecoderMemoryInfo(const OrbisVideodec2DecoderConfigInfo* decoderCfgInfo, - OrbisVideodec2DecoderMemoryInfo* decoderMemInfo); - -s32 PS4_SYSV_ABI sceVideodec2CreateDecoder(const OrbisVideodec2DecoderConfigInfo* decoderCfgInfo, - const OrbisVideodec2DecoderMemoryInfo* decoderMemInfo, - OrbisVideodec2Decoder* decoder); - -s32 PS4_SYSV_ABI sceVideodec2DeleteDecoder(OrbisVideodec2Decoder decoder); - -s32 PS4_SYSV_ABI sceVideodec2Decode(OrbisVideodec2Decoder decoder, - const OrbisVideodec2InputData* inputData, - OrbisVideodec2FrameBuffer* frameBuffer, - OrbisVideodec2OutputInfo* outputInfo); - -s32 PS4_SYSV_ABI sceVideodec2Flush(OrbisVideodec2Decoder decoder, - OrbisVideodec2FrameBuffer* frameBuffer, - OrbisVideodec2OutputInfo* outputInfo); - -s32 PS4_SYSV_ABI sceVideodec2Reset(OrbisVideodec2Decoder decoder); - -s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outputInfo, - void* p1stPictureInfo, void* p2ndPictureInfo); - -void RegisterlibSceVdec2(Core::Loader::SymbolsResolver* sym); +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +#include "videodec2_avc.h" + +namespace Core::Loader { +class SymbolsResolver; +} +namespace Libraries::Vdec2 { + +class VdecDecoder; + +using OrbisVideodec2Decoder = VdecDecoder*; +using OrbisVideodec2ComputeQueue = void*; + +struct OrbisVideodec2DecoderConfigInfo { + u64 thisSize; + u32 resourceType; + u32 codecType; + u32 profile; + u32 maxLevel; + s32 maxFrameWidth; + s32 maxFrameHeight; + s32 maxDpbFrameCount; + u32 decodePipelineDepth; + OrbisVideodec2ComputeQueue computeQueue; + u64 cpuAffinityMask; + s32 cpuThreadPriority; + bool optimizeProgressiveVideo; + bool checkMemoryType; + u8 reserved0; + u8 reserved1; + void* extraConfigInfo; +}; +static_assert(sizeof(OrbisVideodec2DecoderConfigInfo) == 0x48); + +struct OrbisVideodec2DecoderMemoryInfo { + u64 thisSize; + u64 cpuMemorySize; + void* cpuMemory; + u64 gpuMemorySize; + void* gpuMemory; + u64 cpuGpuMemorySize; + void* cpuGpuMemory; + u64 maxFrameBufferSize; + u32 frameBufferAlignment; + u32 reserved0; +}; +static_assert(sizeof(OrbisVideodec2DecoderMemoryInfo) == 0x48); + +struct OrbisVideodec2InputData { + u64 thisSize; + void* auData; + u64 auSize; + u64 ptsData; + u64 dtsData; + u64 attachedData; +}; +static_assert(sizeof(OrbisVideodec2InputData) == 0x30); + +struct OrbisVideodec2OutputInfo { + u64 thisSize; + bool isValid; + bool isErrorFrame; + u8 pictureCount; + u32 codecType; + u32 frameWidth; + u32 framePitch; + u32 frameHeight; + void* frameBuffer; + u64 frameBufferSize; +}; +static_assert(sizeof(OrbisVideodec2OutputInfo) == 0x30); + +struct OrbisVideodec2FrameBuffer { + u64 thisSize; + void* frameBuffer; + u64 frameBufferSize; + bool isAccepted; +}; +static_assert(sizeof(OrbisVideodec2FrameBuffer) == 0x20); + +struct OrbisVideodec2ComputeMemoryInfo { + u64 thisSize; + u64 cpuGpuMemorySize; + void* cpuGpuMemory; +}; +static_assert(sizeof(OrbisVideodec2ComputeMemoryInfo) == 0x18); + +struct OrbisVideodec2ComputeConfigInfo { + u64 thisSize; + u16 computePipeId; + u16 computeQueueId; + bool checkMemoryType; + u8 reserved0; + u16 reserved1; +}; +static_assert(sizeof(OrbisVideodec2ComputeConfigInfo) == 0x10); + +s32 PS4_SYSV_ABI +sceVideodec2QueryComputeMemoryInfo(OrbisVideodec2ComputeMemoryInfo* computeMemInfo); + +s32 PS4_SYSV_ABI +sceVideodec2AllocateComputeQueue(const OrbisVideodec2ComputeConfigInfo* computeCfgInfo, + const OrbisVideodec2ComputeMemoryInfo* computeMemInfo, + OrbisVideodec2ComputeQueue* computeQueue); + +s32 PS4_SYSV_ABI sceVideodec2ReleaseComputeQueue(OrbisVideodec2ComputeQueue computeQueue); + +s32 PS4_SYSV_ABI +sceVideodec2QueryDecoderMemoryInfo(const OrbisVideodec2DecoderConfigInfo* decoderCfgInfo, + OrbisVideodec2DecoderMemoryInfo* decoderMemInfo); + +s32 PS4_SYSV_ABI sceVideodec2CreateDecoder(const OrbisVideodec2DecoderConfigInfo* decoderCfgInfo, + const OrbisVideodec2DecoderMemoryInfo* decoderMemInfo, + OrbisVideodec2Decoder* decoder); + +s32 PS4_SYSV_ABI sceVideodec2DeleteDecoder(OrbisVideodec2Decoder decoder); + +s32 PS4_SYSV_ABI sceVideodec2Decode(OrbisVideodec2Decoder decoder, + const OrbisVideodec2InputData* inputData, + OrbisVideodec2FrameBuffer* frameBuffer, + OrbisVideodec2OutputInfo* outputInfo); + +s32 PS4_SYSV_ABI sceVideodec2Flush(OrbisVideodec2Decoder decoder, + OrbisVideodec2FrameBuffer* frameBuffer, + OrbisVideodec2OutputInfo* outputInfo); + +s32 PS4_SYSV_ABI sceVideodec2Reset(OrbisVideodec2Decoder decoder); + +s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outputInfo, + void* p1stPictureInfo, void* p2ndPictureInfo); + +void RegisterlibSceVdec2(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Vdec2 \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec2_avc.h b/src/core/libraries/videodec/videodec2_avc.h index 4109b5fd2..22293ee93 100644 --- a/src/core/libraries/videodec/videodec2_avc.h +++ b/src/core/libraries/videodec/videodec2_avc.h @@ -1,60 +1,60 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" - -namespace Libraries::Vdec2 { - -struct OrbisVideodec2AvcPictureInfo { - u64 thisSize; - - bool isValid; - - u64 ptsData; - u64 dtsData; - u64 attachedData; - - u8 idrPictureflag; - - u8 profile_idc; - u8 level_idc; - u32 pic_width_in_mbs_minus1; - u32 pic_height_in_map_units_minus1; - u8 frame_mbs_only_flag; - - u8 frame_cropping_flag; - u32 frameCropLeftOffset; - u32 frameCropRightOffset; - u32 frameCropTopOffset; - u32 frameCropBottomOffset; - - u8 aspect_ratio_info_present_flag; - u8 aspect_ratio_idc; - u16 sar_width; - u16 sar_height; - - u8 video_signal_type_present_flag; - u8 video_format; - u8 video_full_range_flag; - u8 colour_description_present_flag; - u8 colour_primaries; - u8 transfer_characteristics; - u8 matrix_coefficients; - - u8 timing_info_present_flag; - u32 num_units_in_tick; - u32 time_scale; - u8 fixed_frame_rate_flag; - - u8 bitstream_restriction_flag; - u8 max_dec_frame_buffering; - - u8 pic_struct_present_flag; - u8 pic_struct; - u8 field_pic_flag; - u8 bottom_field_flag; -}; - +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Libraries::Vdec2 { + +struct OrbisVideodec2AvcPictureInfo { + u64 thisSize; + + bool isValid; + + u64 ptsData; + u64 dtsData; + u64 attachedData; + + u8 idrPictureflag; + + u8 profile_idc; + u8 level_idc; + u32 pic_width_in_mbs_minus1; + u32 pic_height_in_map_units_minus1; + u8 frame_mbs_only_flag; + + u8 frame_cropping_flag; + u32 frameCropLeftOffset; + u32 frameCropRightOffset; + u32 frameCropTopOffset; + u32 frameCropBottomOffset; + + u8 aspect_ratio_info_present_flag; + u8 aspect_ratio_idc; + u16 sar_width; + u16 sar_height; + + u8 video_signal_type_present_flag; + u8 video_format; + u8 video_full_range_flag; + u8 colour_description_present_flag; + u8 colour_primaries; + u8 transfer_characteristics; + u8 matrix_coefficients; + + u8 timing_info_present_flag; + u32 num_units_in_tick; + u32 time_scale; + u8 fixed_frame_rate_flag; + + u8 bitstream_restriction_flag; + u8 max_dec_frame_buffering; + + u8 pic_struct_present_flag; + u8 pic_struct; + u8 field_pic_flag; + u8 bottom_field_flag; +}; + } // namespace Libraries::Vdec2 \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec2_impl.cpp b/src/core/libraries/videodec/videodec2_impl.cpp index 138d78af3..22b17c86c 100644 --- a/src/core/libraries/videodec/videodec2_impl.cpp +++ b/src/core/libraries/videodec/videodec2_impl.cpp @@ -1,229 +1,229 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "videodec2_impl.h" - -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/libraries/videodec/videodec_error.h" - -#include "common/support/avdec.h" - -namespace Libraries::Vdec2 { - -std::vector gPictureInfos; - -static inline void CopyNV12Data(u8* dst, const AVFrame& src) { - std::memcpy(dst, src.data[0], src.width * src.height); - std::memcpy(dst + (src.width * src.height), src.data[1], (src.width * src.height) / 2); -} - -VdecDecoder::VdecDecoder(const OrbisVideodec2DecoderConfigInfo& configInfo, - const OrbisVideodec2DecoderMemoryInfo& memoryInfo) { - ASSERT(configInfo.codecType == 1); /* AVC */ - - const AVCodec* codec = avcodec_find_decoder(AV_CODEC_ID_H264); - ASSERT(codec); - - mCodecContext = avcodec_alloc_context3(codec); - ASSERT(mCodecContext); - mCodecContext->width = configInfo.maxFrameWidth; - mCodecContext->height = configInfo.maxFrameHeight; - - avcodec_open2(mCodecContext, codec, nullptr); -} - -VdecDecoder::~VdecDecoder() { - avcodec_free_context(&mCodecContext); - sws_freeContext(mSwsContext); - - gPictureInfos.clear(); -} - -s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData, - OrbisVideodec2FrameBuffer& frameBuffer, - OrbisVideodec2OutputInfo& outputInfo) { - frameBuffer.isAccepted = false; - outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo); - outputInfo.isValid = false; - outputInfo.isErrorFrame = true; - outputInfo.pictureCount = 0; - - if (!inputData.auData) { - return ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT_POINTER; - } - if (inputData.auSize == 0) { - return ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT_SIZE; - } - - AVPacket* packet = av_packet_alloc(); - if (!packet) { - LOG_ERROR(Lib_Vdec2, "Failed to allocate packet"); - return ORBIS_VIDEODEC2_ERROR_API_FAIL; - } - - packet->data = (u8*)inputData.auData; - packet->size = inputData.auSize; - packet->pts = inputData.ptsData; - packet->dts = inputData.dtsData; - - int ret = avcodec_send_packet(mCodecContext, packet); - if (ret < 0) { - LOG_ERROR(Lib_Vdec2, "Error sending packet to decoder: {}", ret); - av_packet_free(&packet); - return ORBIS_VIDEODEC2_ERROR_API_FAIL; - } - - AVFrame* frame = av_frame_alloc(); - if (frame == nullptr) { - LOG_ERROR(Lib_Vdec2, "Failed to allocate frame"); - av_packet_free(&packet); - return ORBIS_VIDEODEC2_ERROR_API_FAIL; - } - - while (true) { - ret = avcodec_receive_frame(mCodecContext, frame); - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { - break; - } else if (ret < 0) { - LOG_ERROR(Lib_Vdec2, "Error receiving frame from decoder: {}", ret); - av_packet_free(&packet); - av_frame_free(&frame); - return ORBIS_VIDEODEC2_ERROR_API_FAIL; - } - - if (frame->format != AV_PIX_FMT_NV12) { - AVFrame* nv12_frame = ConvertNV12Frame(*frame); - ASSERT(nv12_frame); - av_frame_free(&frame); - frame = nv12_frame; - } - - CopyNV12Data((u8*)frameBuffer.frameBuffer, *frame); - frameBuffer.isAccepted = true; - - outputInfo.codecType = 1; // FIXME: Hardcoded to AVC - outputInfo.frameWidth = frame->width; - outputInfo.frameHeight = frame->height; - outputInfo.framePitch = frame->linesize[0]; - outputInfo.frameBufferSize = frameBuffer.frameBufferSize; - outputInfo.frameBuffer = frameBuffer.frameBuffer; - - outputInfo.isValid = true; - outputInfo.isErrorFrame = false; - outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video - - if (outputInfo.isValid) { - OrbisVideodec2AvcPictureInfo pictureInfo = {}; - - pictureInfo.thisSize = sizeof(OrbisVideodec2AvcPictureInfo); - pictureInfo.isValid = true; - - pictureInfo.ptsData = inputData.ptsData; - pictureInfo.dtsData = inputData.dtsData; - pictureInfo.attachedData = inputData.attachedData; - - pictureInfo.frameCropLeftOffset = frame->crop_left; - pictureInfo.frameCropRightOffset = frame->crop_right; - pictureInfo.frameCropTopOffset = frame->crop_top; - pictureInfo.frameCropBottomOffset = frame->crop_bottom; - - gPictureInfos.push_back(pictureInfo); - } - } - - av_packet_free(&packet); - av_frame_free(&frame); - return ORBIS_OK; -} - -s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer, - OrbisVideodec2OutputInfo& outputInfo) { - frameBuffer.isAccepted = false; - outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo); - outputInfo.isValid = false; - outputInfo.isErrorFrame = true; - outputInfo.pictureCount = 0; - - AVFrame* frame = av_frame_alloc(); - if (!frame) { - LOG_ERROR(Lib_Vdec2, "Failed to allocate frame"); - return ORBIS_VIDEODEC2_ERROR_API_FAIL; - } - - while (true) { - int ret = avcodec_receive_frame(mCodecContext, frame); - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { - break; - } else if (ret < 0) { - LOG_ERROR(Lib_Vdec2, "Error receiving frame from decoder: {}", ret); - av_frame_free(&frame); - return ORBIS_VIDEODEC2_ERROR_API_FAIL; - } - - if (frame->format != AV_PIX_FMT_NV12) { - AVFrame* nv12_frame = ConvertNV12Frame(*frame); - ASSERT(nv12_frame); - av_frame_free(&frame); - frame = nv12_frame; - } - - CopyNV12Data((u8*)frameBuffer.frameBuffer, *frame); - frameBuffer.isAccepted = true; - - outputInfo.codecType = 1; // FIXME: Hardcoded to AVC - outputInfo.frameWidth = frame->width; - outputInfo.frameHeight = frame->height; - outputInfo.framePitch = frame->linesize[0]; - outputInfo.frameBufferSize = frameBuffer.frameBufferSize; - outputInfo.frameBuffer = frameBuffer.frameBuffer; - - outputInfo.isValid = true; - outputInfo.isErrorFrame = false; - outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video - - // FIXME: Should we add picture info here too? - } - - av_frame_free(&frame); - return ORBIS_OK; -} - -s32 VdecDecoder::Reset() { - avcodec_flush_buffers(mCodecContext); - gPictureInfos.clear(); - return ORBIS_OK; -} - -AVFrame* VdecDecoder::ConvertNV12Frame(AVFrame& frame) { - AVFrame* nv12_frame = av_frame_alloc(); - nv12_frame->pts = frame.pts; - nv12_frame->pkt_dts = frame.pkt_dts < 0 ? 0 : frame.pkt_dts; - nv12_frame->format = AV_PIX_FMT_NV12; - nv12_frame->width = frame.width; - nv12_frame->height = frame.height; - nv12_frame->sample_aspect_ratio = frame.sample_aspect_ratio; - nv12_frame->crop_top = frame.crop_top; - nv12_frame->crop_bottom = frame.crop_bottom; - nv12_frame->crop_left = frame.crop_left; - nv12_frame->crop_right = frame.crop_right; - - av_frame_get_buffer(nv12_frame, 0); - - if (mSwsContext == nullptr) { - mSwsContext = sws_getContext(frame.width, frame.height, AVPixelFormat(frame.format), - nv12_frame->width, nv12_frame->height, AV_PIX_FMT_NV12, - SWS_FAST_BILINEAR, nullptr, nullptr, nullptr); - } - - const auto res = sws_scale(mSwsContext, frame.data, frame.linesize, 0, frame.height, - nv12_frame->data, nv12_frame->linesize); - if (res < 0) { - LOG_ERROR(Lib_Vdec2, "Could not convert to NV12: {}", av_err2str(res)); - return nullptr; - } - - return nv12_frame; -} - -} // namespace Libraries::Vdec2 +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "videodec2_impl.h" + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/libraries/videodec/videodec_error.h" + +#include "common/support/avdec.h" + +namespace Libraries::Vdec2 { + +std::vector gPictureInfos; + +static inline void CopyNV12Data(u8* dst, const AVFrame& src) { + std::memcpy(dst, src.data[0], src.width * src.height); + std::memcpy(dst + (src.width * src.height), src.data[1], (src.width * src.height) / 2); +} + +VdecDecoder::VdecDecoder(const OrbisVideodec2DecoderConfigInfo& configInfo, + const OrbisVideodec2DecoderMemoryInfo& memoryInfo) { + ASSERT(configInfo.codecType == 1); /* AVC */ + + const AVCodec* codec = avcodec_find_decoder(AV_CODEC_ID_H264); + ASSERT(codec); + + mCodecContext = avcodec_alloc_context3(codec); + ASSERT(mCodecContext); + mCodecContext->width = configInfo.maxFrameWidth; + mCodecContext->height = configInfo.maxFrameHeight; + + avcodec_open2(mCodecContext, codec, nullptr); +} + +VdecDecoder::~VdecDecoder() { + avcodec_free_context(&mCodecContext); + sws_freeContext(mSwsContext); + + gPictureInfos.clear(); +} + +s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData, + OrbisVideodec2FrameBuffer& frameBuffer, + OrbisVideodec2OutputInfo& outputInfo) { + frameBuffer.isAccepted = false; + outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo); + outputInfo.isValid = false; + outputInfo.isErrorFrame = true; + outputInfo.pictureCount = 0; + + if (!inputData.auData) { + return ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT_POINTER; + } + if (inputData.auSize == 0) { + return ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT_SIZE; + } + + AVPacket* packet = av_packet_alloc(); + if (!packet) { + LOG_ERROR(Lib_Vdec2, "Failed to allocate packet"); + return ORBIS_VIDEODEC2_ERROR_API_FAIL; + } + + packet->data = (u8*)inputData.auData; + packet->size = inputData.auSize; + packet->pts = inputData.ptsData; + packet->dts = inputData.dtsData; + + int ret = avcodec_send_packet(mCodecContext, packet); + if (ret < 0) { + LOG_ERROR(Lib_Vdec2, "Error sending packet to decoder: {}", ret); + av_packet_free(&packet); + return ORBIS_VIDEODEC2_ERROR_API_FAIL; + } + + AVFrame* frame = av_frame_alloc(); + if (frame == nullptr) { + LOG_ERROR(Lib_Vdec2, "Failed to allocate frame"); + av_packet_free(&packet); + return ORBIS_VIDEODEC2_ERROR_API_FAIL; + } + + while (true) { + ret = avcodec_receive_frame(mCodecContext, frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } else if (ret < 0) { + LOG_ERROR(Lib_Vdec2, "Error receiving frame from decoder: {}", ret); + av_packet_free(&packet); + av_frame_free(&frame); + return ORBIS_VIDEODEC2_ERROR_API_FAIL; + } + + if (frame->format != AV_PIX_FMT_NV12) { + AVFrame* nv12_frame = ConvertNV12Frame(*frame); + ASSERT(nv12_frame); + av_frame_free(&frame); + frame = nv12_frame; + } + + CopyNV12Data((u8*)frameBuffer.frameBuffer, *frame); + frameBuffer.isAccepted = true; + + outputInfo.codecType = 1; // FIXME: Hardcoded to AVC + outputInfo.frameWidth = frame->width; + outputInfo.frameHeight = frame->height; + outputInfo.framePitch = frame->linesize[0]; + outputInfo.frameBufferSize = frameBuffer.frameBufferSize; + outputInfo.frameBuffer = frameBuffer.frameBuffer; + + outputInfo.isValid = true; + outputInfo.isErrorFrame = false; + outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video + + if (outputInfo.isValid) { + OrbisVideodec2AvcPictureInfo pictureInfo = {}; + + pictureInfo.thisSize = sizeof(OrbisVideodec2AvcPictureInfo); + pictureInfo.isValid = true; + + pictureInfo.ptsData = inputData.ptsData; + pictureInfo.dtsData = inputData.dtsData; + pictureInfo.attachedData = inputData.attachedData; + + pictureInfo.frameCropLeftOffset = frame->crop_left; + pictureInfo.frameCropRightOffset = frame->crop_right; + pictureInfo.frameCropTopOffset = frame->crop_top; + pictureInfo.frameCropBottomOffset = frame->crop_bottom; + + gPictureInfos.push_back(pictureInfo); + } + } + + av_packet_free(&packet); + av_frame_free(&frame); + return ORBIS_OK; +} + +s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer, + OrbisVideodec2OutputInfo& outputInfo) { + frameBuffer.isAccepted = false; + outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo); + outputInfo.isValid = false; + outputInfo.isErrorFrame = true; + outputInfo.pictureCount = 0; + + AVFrame* frame = av_frame_alloc(); + if (!frame) { + LOG_ERROR(Lib_Vdec2, "Failed to allocate frame"); + return ORBIS_VIDEODEC2_ERROR_API_FAIL; + } + + while (true) { + int ret = avcodec_receive_frame(mCodecContext, frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } else if (ret < 0) { + LOG_ERROR(Lib_Vdec2, "Error receiving frame from decoder: {}", ret); + av_frame_free(&frame); + return ORBIS_VIDEODEC2_ERROR_API_FAIL; + } + + if (frame->format != AV_PIX_FMT_NV12) { + AVFrame* nv12_frame = ConvertNV12Frame(*frame); + ASSERT(nv12_frame); + av_frame_free(&frame); + frame = nv12_frame; + } + + CopyNV12Data((u8*)frameBuffer.frameBuffer, *frame); + frameBuffer.isAccepted = true; + + outputInfo.codecType = 1; // FIXME: Hardcoded to AVC + outputInfo.frameWidth = frame->width; + outputInfo.frameHeight = frame->height; + outputInfo.framePitch = frame->linesize[0]; + outputInfo.frameBufferSize = frameBuffer.frameBufferSize; + outputInfo.frameBuffer = frameBuffer.frameBuffer; + + outputInfo.isValid = true; + outputInfo.isErrorFrame = false; + outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video + + // FIXME: Should we add picture info here too? + } + + av_frame_free(&frame); + return ORBIS_OK; +} + +s32 VdecDecoder::Reset() { + avcodec_flush_buffers(mCodecContext); + gPictureInfos.clear(); + return ORBIS_OK; +} + +AVFrame* VdecDecoder::ConvertNV12Frame(AVFrame& frame) { + AVFrame* nv12_frame = av_frame_alloc(); + nv12_frame->pts = frame.pts; + nv12_frame->pkt_dts = frame.pkt_dts < 0 ? 0 : frame.pkt_dts; + nv12_frame->format = AV_PIX_FMT_NV12; + nv12_frame->width = frame.width; + nv12_frame->height = frame.height; + nv12_frame->sample_aspect_ratio = frame.sample_aspect_ratio; + nv12_frame->crop_top = frame.crop_top; + nv12_frame->crop_bottom = frame.crop_bottom; + nv12_frame->crop_left = frame.crop_left; + nv12_frame->crop_right = frame.crop_right; + + av_frame_get_buffer(nv12_frame, 0); + + if (mSwsContext == nullptr) { + mSwsContext = sws_getContext(frame.width, frame.height, AVPixelFormat(frame.format), + nv12_frame->width, nv12_frame->height, AV_PIX_FMT_NV12, + SWS_FAST_BILINEAR, nullptr, nullptr, nullptr); + } + + const auto res = sws_scale(mSwsContext, frame.data, frame.linesize, 0, frame.height, + nv12_frame->data, nv12_frame->linesize); + if (res < 0) { + LOG_ERROR(Lib_Vdec2, "Could not convert to NV12: {}", av_err2str(res)); + return nullptr; + } + + return nv12_frame; +} + +} // namespace Libraries::Vdec2 diff --git a/src/core/libraries/videodec/videodec2_impl.h b/src/core/libraries/videodec/videodec2_impl.h index 1bcece6e1..c8e8ea253 100644 --- a/src/core/libraries/videodec/videodec2_impl.h +++ b/src/core/libraries/videodec/videodec2_impl.h @@ -1,39 +1,39 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "videodec2.h" - -extern "C" { -#include -#include -#include -} - -namespace Libraries::Vdec2 { - -extern std::vector gPictureInfos; - -class VdecDecoder { -public: - VdecDecoder(const OrbisVideodec2DecoderConfigInfo& configInfo, - const OrbisVideodec2DecoderMemoryInfo& memoryInfo); - ~VdecDecoder(); - - s32 Decode(const OrbisVideodec2InputData& inputData, OrbisVideodec2FrameBuffer& frameBuffer, - OrbisVideodec2OutputInfo& outputInfo); - s32 Flush(OrbisVideodec2FrameBuffer& frameBuffer, OrbisVideodec2OutputInfo& outputInfo); - s32 Reset(); - -private: - AVFrame* ConvertNV12Frame(AVFrame& frame); - -private: - AVCodecContext* mCodecContext = nullptr; - SwsContext* mSwsContext = nullptr; -}; - +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "videodec2.h" + +extern "C" { +#include +#include +#include +} + +namespace Libraries::Vdec2 { + +extern std::vector gPictureInfos; + +class VdecDecoder { +public: + VdecDecoder(const OrbisVideodec2DecoderConfigInfo& configInfo, + const OrbisVideodec2DecoderMemoryInfo& memoryInfo); + ~VdecDecoder(); + + s32 Decode(const OrbisVideodec2InputData& inputData, OrbisVideodec2FrameBuffer& frameBuffer, + OrbisVideodec2OutputInfo& outputInfo); + s32 Flush(OrbisVideodec2FrameBuffer& frameBuffer, OrbisVideodec2OutputInfo& outputInfo); + s32 Reset(); + +private: + AVFrame* ConvertNV12Frame(AVFrame& frame); + +private: + AVCodecContext* mCodecContext = nullptr; + SwsContext* mSwsContext = nullptr; +}; + } // namespace Libraries::Vdec2 \ No newline at end of file diff --git a/src/core/thread.cpp b/src/core/thread.cpp index 07681e6b9..e154530d5 100644 --- a/src/core/thread.cpp +++ b/src/core/thread.cpp @@ -1,151 +1,152 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/alignment.h" -#include "core/libraries/kernel/threads/pthread.h" -#include "thread.h" - -#ifdef _WIN64 -#include -#include "common/ntapi.h" -#else -#include -#include -#endif - -namespace Core { - -#ifdef _WIN64 -#define KGDT64_R3_DATA (0x28) -#define KGDT64_R3_CODE (0x30) -#define KGDT64_R3_CMTEB (0x50) -#define RPL_MASK (0x03) - -#define INITIAL_FPUCW (0x037f) -#define INITIAL_MXCSR_MASK (0xffbf) -#define EFLAGS_INTERRUPT_MASK (0x200) - -void InitializeTeb(INITIAL_TEB* teb, const ::Libraries::Kernel::PthreadAttr* attr) { - teb->StackBase = (void*)((u64)attr->stackaddr_attr + attr->stacksize_attr); - teb->StackLimit = nullptr; - teb->StackAllocationBase = attr->stackaddr_attr; -} - -void InitializeContext(CONTEXT* ctx, ThreadFunc func, void* arg, - const ::Libraries::Kernel::PthreadAttr* attr) { - /* Note: The stack has to be reversed */ - ctx->Rsp = (u64)attr->stackaddr_attr + attr->stacksize_attr; - ctx->Rbp = (u64)attr->stackaddr_attr + attr->stacksize_attr; - ctx->Rcx = (u64)arg; - ctx->Rip = (u64)func; - - ctx->SegGs = KGDT64_R3_DATA | RPL_MASK; - ctx->SegEs = KGDT64_R3_DATA | RPL_MASK; - ctx->SegDs = KGDT64_R3_DATA | RPL_MASK; - ctx->SegCs = KGDT64_R3_CODE | RPL_MASK; - ctx->SegSs = KGDT64_R3_DATA | RPL_MASK; - ctx->SegFs = KGDT64_R3_CMTEB | RPL_MASK; - - ctx->EFlags = 0x3000 | EFLAGS_INTERRUPT_MASK; - ctx->MxCsr = INITIAL_MXCSR; - - ctx->FltSave.ControlWord = INITIAL_FPUCW; - ctx->FltSave.MxCsr = INITIAL_MXCSR; - ctx->FltSave.MxCsr_Mask = INITIAL_MXCSR_MASK; - - ctx->ContextFlags = - CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT; -} -#endif - -NativeThread::NativeThread() : native_handle{0} {} - -NativeThread::~NativeThread() {} - -int NativeThread::Create(ThreadFunc func, void* arg, const ::Libraries::Kernel::PthreadAttr* attr) { -#ifndef _WIN64 - pthread_t* pthr = reinterpret_cast(&native_handle); - pthread_attr_t pattr; - pthread_attr_init(&pattr); - pthread_attr_setstack(&pattr, attr->stackaddr_attr, attr->stacksize_attr); - return pthread_create(pthr, &pattr, (PthreadFunc)func, arg); -#else - CLIENT_ID clientId{}; - INITIAL_TEB teb{}; - CONTEXT ctx{}; - - clientId.UniqueProcess = GetCurrentProcess(); - clientId.UniqueThread = GetCurrentThread(); - - InitializeTeb(&teb, attr); - InitializeContext(&ctx, func, arg, attr); - - return NtCreateThread(&native_handle, THREAD_ALL_ACCESS, nullptr, GetCurrentProcess(), - &clientId, &ctx, &teb, false); -#endif -} - -void NativeThread::Exit() { - if (!native_handle) { - return; - } - - tid = 0; - -#ifdef _WIN64 - NtClose(native_handle); - native_handle = nullptr; - - /* The Windows kernel will free the stack - given at thread creation via INITIAL_TEB - (StackAllocationBase) upon thread termination. - - In earlier Windows versions (NT4 to Windows Server 2003), - you could get around this via disabling FreeStackOnTermination - on the TEB. This has been removed since then. - - To avoid this, we must forcefully set the TEB - deallocation stack pointer to NULL so ZwFreeVirtualMemory fails - in the kernel and our stack is not freed. - */ - auto* teb = reinterpret_cast(NtCurrentTeb()); - teb->DeallocationStack = nullptr; - - NtTerminateThread(nullptr, 0); -#else - // Disable and free the signal stack. - constexpr stack_t sig_stack = { - .ss_flags = SS_DISABLE, - }; - sigaltstack(&sig_stack, nullptr); - - if (sig_stack_ptr) { - free(sig_stack_ptr); - sig_stack_ptr = nullptr; - } - - pthread_exit(nullptr); -#endif -} - -void NativeThread::Initialize() { -#if _WIN64 - tid = GetCurrentThreadId(); -#else - tid = (u64)pthread_self(); - - // Set up an alternate signal handler stack to avoid overflowing small thread stacks. - const size_t page_size = getpagesize(); - const size_t sig_stack_size = Common::AlignUp(std::max(64_KB, MINSIGSTKSZ), page_size); - ASSERT_MSG(posix_memalign(&sig_stack_ptr, page_size, sig_stack_size) == 0, - "Failed to allocate signal stack: {}", errno); - - stack_t sig_stack; - sig_stack.ss_sp = sig_stack_ptr; - sig_stack.ss_size = sig_stack_size; - sig_stack.ss_flags = 0; - ASSERT_MSG(sigaltstack(&sig_stack, nullptr) == 0, "Failed to set signal stack: {}", errno); -#endif -} - -} // namespace Core +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/alignment.h" +#include "core/libraries/kernel/threads/pthread.h" +#include "thread.h" + +#ifdef _WIN64 +#include +#include "common/ntapi.h" +#else +#include +#include +#include +#endif + +namespace Core { + +#ifdef _WIN64 +#define KGDT64_R3_DATA (0x28) +#define KGDT64_R3_CODE (0x30) +#define KGDT64_R3_CMTEB (0x50) +#define RPL_MASK (0x03) + +#define INITIAL_FPUCW (0x037f) +#define INITIAL_MXCSR_MASK (0xffbf) +#define EFLAGS_INTERRUPT_MASK (0x200) + +void InitializeTeb(INITIAL_TEB* teb, const ::Libraries::Kernel::PthreadAttr* attr) { + teb->StackBase = (void*)((u64)attr->stackaddr_attr + attr->stacksize_attr); + teb->StackLimit = nullptr; + teb->StackAllocationBase = attr->stackaddr_attr; +} + +void InitializeContext(CONTEXT* ctx, ThreadFunc func, void* arg, + const ::Libraries::Kernel::PthreadAttr* attr) { + /* Note: The stack has to be reversed */ + ctx->Rsp = (u64)attr->stackaddr_attr + attr->stacksize_attr; + ctx->Rbp = (u64)attr->stackaddr_attr + attr->stacksize_attr; + ctx->Rcx = (u64)arg; + ctx->Rip = (u64)func; + + ctx->SegGs = KGDT64_R3_DATA | RPL_MASK; + ctx->SegEs = KGDT64_R3_DATA | RPL_MASK; + ctx->SegDs = KGDT64_R3_DATA | RPL_MASK; + ctx->SegCs = KGDT64_R3_CODE | RPL_MASK; + ctx->SegSs = KGDT64_R3_DATA | RPL_MASK; + ctx->SegFs = KGDT64_R3_CMTEB | RPL_MASK; + + ctx->EFlags = 0x3000 | EFLAGS_INTERRUPT_MASK; + ctx->MxCsr = INITIAL_MXCSR; + + ctx->FltSave.ControlWord = INITIAL_FPUCW; + ctx->FltSave.MxCsr = INITIAL_MXCSR; + ctx->FltSave.MxCsr_Mask = INITIAL_MXCSR_MASK; + + ctx->ContextFlags = + CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT; +} +#endif + +NativeThread::NativeThread() : native_handle{0} {} + +NativeThread::~NativeThread() {} + +int NativeThread::Create(ThreadFunc func, void* arg, const ::Libraries::Kernel::PthreadAttr* attr) { +#ifndef _WIN64 + pthread_t* pthr = reinterpret_cast(&native_handle); + pthread_attr_t pattr; + pthread_attr_init(&pattr); + pthread_attr_setstack(&pattr, attr->stackaddr_attr, attr->stacksize_attr); + return pthread_create(pthr, &pattr, (PthreadFunc)func, arg); +#else + CLIENT_ID clientId{}; + INITIAL_TEB teb{}; + CONTEXT ctx{}; + + clientId.UniqueProcess = GetCurrentProcess(); + clientId.UniqueThread = GetCurrentThread(); + + InitializeTeb(&teb, attr); + InitializeContext(&ctx, func, arg, attr); + + return NtCreateThread(&native_handle, THREAD_ALL_ACCESS, nullptr, GetCurrentProcess(), + &clientId, &ctx, &teb, false); +#endif +} + +void NativeThread::Exit() { + if (!native_handle) { + return; + } + + tid = 0; + +#ifdef _WIN64 + NtClose(native_handle); + native_handle = nullptr; + + /* The Windows kernel will free the stack + given at thread creation via INITIAL_TEB + (StackAllocationBase) upon thread termination. + + In earlier Windows versions (NT4 to Windows Server 2003), + you could get around this via disabling FreeStackOnTermination + on the TEB. This has been removed since then. + + To avoid this, we must forcefully set the TEB + deallocation stack pointer to NULL so ZwFreeVirtualMemory fails + in the kernel and our stack is not freed. + */ + auto* teb = reinterpret_cast(NtCurrentTeb()); + teb->DeallocationStack = nullptr; + + NtTerminateThread(nullptr, 0); +#else + // Disable and free the signal stack. + constexpr stack_t sig_stack = { + .ss_flags = SS_DISABLE, + }; + sigaltstack(&sig_stack, nullptr); + + if (sig_stack_ptr) { + free(sig_stack_ptr); + sig_stack_ptr = nullptr; + } + + pthread_exit(nullptr); +#endif +} + +void NativeThread::Initialize() { +#if _WIN64 + tid = GetCurrentThreadId(); +#else + tid = (u64)pthread_self(); + + // Set up an alternate signal handler stack to avoid overflowing small thread stacks. + const size_t page_size = getpagesize(); + const size_t sig_stack_size = Common::AlignUp(std::max(64_KB, MINSIGSTKSZ), page_size); + ASSERT_MSG(posix_memalign(&sig_stack_ptr, page_size, sig_stack_size) == 0, + "Failed to allocate signal stack: {}", errno); + + stack_t sig_stack; + sig_stack.ss_sp = sig_stack_ptr; + sig_stack.ss_size = sig_stack_size; + sig_stack.ss_flags = 0; + ASSERT_MSG(sigaltstack(&sig_stack, nullptr) == 0, "Failed to set signal stack: {}", errno); +#endif +} + +} // namespace Core diff --git a/src/core/thread.h b/src/core/thread.h index bd777a2e6..6ea7dfad4 100644 --- a/src/core/thread.h +++ b/src/core/thread.h @@ -1,45 +1,45 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" - -namespace Libraries::Kernel { -struct PthreadAttr; -} // namespace Libraries::Kernel - -namespace Core { - -using ThreadFunc = void (*)(void*); -using PthreadFunc = void* (*)(void*); - -class NativeThread { -public: - NativeThread(); - ~NativeThread(); - - int Create(ThreadFunc func, void* arg, const ::Libraries::Kernel::PthreadAttr* attr); - void Exit(); - - void Initialize(); - - uintptr_t GetHandle() { - return reinterpret_cast(native_handle); - } - - u64 GetTid() { - return tid; - } - -private: -#ifdef _WIN64 - void* native_handle; -#else - uintptr_t native_handle; - void* sig_stack_ptr; -#endif - u64 tid; -}; - +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Libraries::Kernel { +struct PthreadAttr; +} // namespace Libraries::Kernel + +namespace Core { + +using ThreadFunc = void (*)(void*); +using PthreadFunc = void* (*)(void*); + +class NativeThread { +public: + NativeThread(); + ~NativeThread(); + + int Create(ThreadFunc func, void* arg, const ::Libraries::Kernel::PthreadAttr* attr); + void Exit(); + + void Initialize(); + + uintptr_t GetHandle() { + return reinterpret_cast(native_handle); + } + + u64 GetTid() { + return tid; + } + +private: +#ifdef _WIN64 + void* native_handle; +#else + uintptr_t native_handle; + void* sig_stack_ptr; +#endif + u64 tid; +}; + } // namespace Core \ No newline at end of file diff --git a/src/core/tls.h b/src/core/tls.h index 4df9e4ace..6edd6a297 100644 --- a/src/core/tls.h +++ b/src/core/tls.h @@ -9,6 +9,10 @@ namespace Xbyak { class CodeGenerator; } +namespace Libraries::Fiber { +struct OrbisFiberContext; +} + namespace Core { union DtvEntry { @@ -20,6 +24,7 @@ struct Tcb { Tcb* tcb_self; DtvEntry* tcb_dtv; void* tcb_thread; + ::Libraries::Fiber::OrbisFiberContext* tcb_fiber; }; #ifdef _WIN32 diff --git a/src/qt_gui/check_update.cpp b/src/qt_gui/check_update.cpp index d713f67fb..edd55b804 100644 --- a/src/qt_gui/check_update.cpp +++ b/src/qt_gui/check_update.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -212,9 +213,9 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate, // Don't show changelog button if: // The current version is a pre-release and the version to be downloaded is a release. - bool current_isRelease = currentRev.startsWith('v', Qt::CaseInsensitive); - bool latest_isRelease = latestRev.startsWith('v', Qt::CaseInsensitive); - if (!current_isRelease && latest_isRelease) { + bool current_isWIP = currentRev.endsWith("WIP", Qt::CaseInsensitive); + bool latest_isWIP = latestRev.endsWith("WIP", Qt::CaseInsensitive); + if (current_isWIP && !latest_isWIP) { } else { QTextEdit* textField = new QTextEdit(this); textField->setReadOnly(true); @@ -348,7 +349,9 @@ void CheckUpdate::DownloadUpdate(const QString& url) { QString userPath; Common::FS::PathToQString(userPath, Common::FS::GetUserPath(Common::FS::PathType::UserDir)); #ifdef Q_OS_WIN - QString tempDownloadPath = QString(getenv("LOCALAPPDATA")) + "/Temp/temp_download_update"; + QString tempDownloadPath = + QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + "/Temp/temp_download_update"; #else QString tempDownloadPath = userPath + "/temp_download_update"; #endif @@ -397,10 +400,11 @@ void CheckUpdate::Install() { QString processCommand; #ifdef Q_OS_WIN - // On windows, overwrite tempDirPath with AppData/Local/Temp folder + // On windows, overwrite tempDirPath with AppData/Roaming/shadps4/Temp folder // due to PowerShell Expand-Archive not being able to handle correctly // paths in square brackets (ie: ./[shadps4]) - tempDirPath = QString(getenv("LOCALAPPDATA")) + "/Temp/temp_download_update"; + tempDirPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + "/Temp/temp_download_update"; // Windows Batch Script scriptFileName = tempDirPath + "/update.ps1"; @@ -536,6 +540,7 @@ void CheckUpdate::Install() { QFile scriptFile(scriptFileName); if (scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(&scriptFile); + scriptFile.write("\xEF\xBB\xBF"); #ifdef Q_OS_WIN out << scriptContent.arg(binaryStartingUpdate).arg(tempDirPath).arg(rootPath); #endif diff --git a/src/qt_gui/compatibility_info.cpp b/src/qt_gui/compatibility_info.cpp index aecac60cd..69fb3e377 100644 --- a/src/qt_gui/compatibility_info.cpp +++ b/src/qt_gui/compatibility_info.cpp @@ -18,9 +18,10 @@ CompatibilityInfoClass::CompatibilityInfoClass() }; CompatibilityInfoClass::~CompatibilityInfoClass() = default; -void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) { - if (LoadCompatibilityFile()) - return; +void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent, bool forced) { + if (!forced) + if (LoadCompatibilityFile()) + return; QNetworkReply* reply = FetchPage(1); if (!WaitForReply(reply)) @@ -45,7 +46,8 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) { QMessageBox::critical(parent, tr("Error"), tr("Unable to update compatibility data! Try again later.")); // Try loading compatibility_file.json again - LoadCompatibilityFile(); + if (!forced) + LoadCompatibilityFile(); return; } diff --git a/src/qt_gui/compatibility_info.h b/src/qt_gui/compatibility_info.h index dcbaef847..0c47c27ff 100644 --- a/src/qt_gui/compatibility_info.h +++ b/src/qt_gui/compatibility_info.h @@ -84,7 +84,7 @@ public: CompatibilityInfoClass(); ~CompatibilityInfoClass(); - void UpdateCompatibilityDatabase(QWidget* parent = nullptr); + void UpdateCompatibilityDatabase(QWidget* parent = nullptr, bool forced = false); bool LoadCompatibilityFile(); CompatibilityEntry GetCompatibilityInfo(const std::string& serial); void ExtractCompatibilityInfo(QByteArray response); diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index b932e46c3..2ebb09e5d 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -3,9 +3,12 @@ #include "common/path_util.h" #include "game_grid_frame.h" +#include "qt_gui/compatibility_info.h" -GameGridFrame::GameGridFrame(std::shared_ptr game_info_get, QWidget* parent) - : QTableWidget(parent), m_game_info(game_info_get) { +GameGridFrame::GameGridFrame(std::shared_ptr game_info_get, + std::shared_ptr compat_info_get, + QWidget* parent) + : QTableWidget(parent), m_game_info(game_info_get), m_compat_info(compat_info_get) { icon_size = Config::getIconSizeGrid(); windowWidth = parent->width(); this->setShowGrid(false); @@ -29,7 +32,7 @@ GameGridFrame::GameGridFrame(std::shared_ptr game_info_get, QWidg connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this, &GameGridFrame::RefreshGridBackgroundImage); connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { - m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, this, false); + m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, this, false); }); } diff --git a/src/qt_gui/game_grid_frame.h b/src/qt_gui/game_grid_frame.h index c09767684..4825d6daf 100644 --- a/src/qt_gui/game_grid_frame.h +++ b/src/qt_gui/game_grid_frame.h @@ -10,6 +10,7 @@ #include "game_info.h" #include "game_list_utils.h" #include "gui_context_menus.h" +#include "qt_gui/compatibility_info.h" class GameGridFrame : public QTableWidget { Q_OBJECT @@ -29,11 +30,14 @@ private: GameListUtils m_game_list_utils; GuiContextMenus m_gui_context_menus; std::shared_ptr m_game_info; + std::shared_ptr m_compat_info; std::shared_ptr> m_games_shared; bool validCellSelected = false; public: - explicit GameGridFrame(std::shared_ptr game_info_get, QWidget* parent = nullptr); + explicit GameGridFrame(std::shared_ptr game_info_get, + std::shared_ptr compat_info_get, + QWidget* parent = nullptr); void PopulateGameGrid(QVector m_games, bool fromSearch); bool IsValidCellSelected(); diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index 53159d8e7..bba3c2891 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include "common/config.h" #include "common/logging/log.h" #include "common/path_util.h" #include "common/string_util.h" @@ -72,7 +73,7 @@ GameListFrame::GameListFrame(std::shared_ptr game_info_get, }); connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { - m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, this, true); + m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, this, true); }); connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) { @@ -80,11 +81,6 @@ GameListFrame::GameListFrame(std::shared_ptr game_info_get, QDesktopServices::openUrl(QUrl(m_game_info->m_games[row].compatibility.url)); } }); - - // Do not show status column if it is not enabled - if (!Config::getCompatibilityEnabled()) { - this->setColumnHidden(2, true); - } } void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow, @@ -108,6 +104,8 @@ void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) { } void GameListFrame::PopulateGameList() { + // Do not show status column if it is not enabled + this->setColumnHidden(2, !Config::getCompatibilityEnabled()); this->setRowCount(m_game_info->m_games.size()); ResizeIcons(icon_size); @@ -241,7 +239,7 @@ void GameListFrame::SetCompatibilityItem(int row, int column, CompatibilityEntry break; case CompatibilityStatus::Nothing: color = QStringLiteral("#212121"); - status_explanation = tr("Games does not initialize properly / crashes the emulator"); + status_explanation = tr("Game does not initialize properly / crashes the emulator"); break; case CompatibilityStatus::Boots: color = QStringLiteral("#828282"); diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 3cc12c11e..6c96ab37f 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -11,6 +11,9 @@ #include #include "cheats_patches.h" +#include "common/config.h" +#include "common/version.h" +#include "compatibility_info.h" #include "game_info.h" #include "trophy_viewer.h" @@ -27,8 +30,9 @@ class GuiContextMenus : public QObject { Q_OBJECT public: - void RequestGameMenu(const QPoint& pos, QVector m_games, QTableWidget* widget, - bool isList) { + void RequestGameMenu(const QPoint& pos, QVector m_games, + std::shared_ptr m_compat_info, + QTableWidget* widget, bool isList) { QPoint global_pos = widget->viewport()->mapToGlobal(pos); int itemID = 0; if (isList) { @@ -91,6 +95,21 @@ public: menu.addMenu(deleteMenu); + // Compatibility submenu. + QMenu* compatibilityMenu = new QMenu(tr("Compatibility..."), widget); + QAction* updateCompatibility = new QAction(tr("Update database"), widget); + QAction* viewCompatibilityReport = new QAction(tr("View report"), widget); + QAction* submitCompatibilityReport = new QAction(tr("Submit a report"), widget); + + compatibilityMenu->addAction(updateCompatibility); + compatibilityMenu->addAction(viewCompatibilityReport); + compatibilityMenu->addAction(submitCompatibilityReport); + + menu.addMenu(compatibilityMenu); + + compatibilityMenu->setEnabled(Config::getCompatibilityEnabled()); + viewCompatibilityReport->setEnabled(!m_games[itemID].compatibility.url.isEmpty()); + // Show menu. auto selected = menu.exec(global_pos); if (!selected) { @@ -268,11 +287,11 @@ public: #endif QMessageBox::information( nullptr, tr("Shortcut creation"), - QString(tr("Shortcut created successfully!\n %1")).arg(linkPath)); + QString(tr("Shortcut created successfully!") + "\n%1").arg(linkPath)); } else { QMessageBox::critical( nullptr, tr("Error"), - QString(tr("Error creating shortcut!\n %1")).arg(linkPath)); + QString(tr("Error creating shortcut!") + "\n%1").arg(linkPath)); } } else { QMessageBox::critical(nullptr, tr("Error"), tr("Failed to convert icon.")); @@ -286,11 +305,11 @@ public: #endif QMessageBox::information( nullptr, tr("Shortcut creation"), - QString(tr("Shortcut created successfully!\n %1")).arg(linkPath)); + QString(tr("Shortcut created successfully!") + "\n%1").arg(linkPath)); } else { QMessageBox::critical( nullptr, tr("Error"), - QString(tr("Error creating shortcut!\n %1")).arg(linkPath)); + QString(tr("Error creating shortcut!") + "\n%1").arg(linkPath)); } } } @@ -360,6 +379,31 @@ public: } } } + + if (selected == updateCompatibility) { + m_compat_info->UpdateCompatibilityDatabase(widget, true); + } + + if (selected == viewCompatibilityReport) { + if (!m_games[itemID].compatibility.url.isEmpty()) + QDesktopServices::openUrl(QUrl(m_games[itemID].compatibility.url)); + } + + if (selected == submitCompatibilityReport) { + QUrl url = QUrl("https://github.com/shadps4-emu/shadps4-game-compatibility/issues/new"); + QUrlQuery query; + query.addQueryItem("template", QString("game_compatibility.yml")); + query.addQueryItem( + "title", QString("%1 - %2").arg(QString::fromStdString(m_games[itemID].serial), + QString::fromStdString(m_games[itemID].name))); + query.addQueryItem("game-name", QString::fromStdString(m_games[itemID].name)); + query.addQueryItem("game-code", QString::fromStdString(m_games[itemID].serial)); + query.addQueryItem("game-version", QString::fromStdString(m_games[itemID].version)); + query.addQueryItem("emulator-version", QString(Common::VERSION)); + url.setQuery(query); + + QDesktopServices::openUrl(url); + } } int GetRowIndex(QTreeWidget* treeWidget, QTreeWidgetItem* item) { diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index fae19581b..3f668fce5 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -153,7 +153,7 @@ void MainWindow::CreateDockWindows() { m_dock_widget.reset(new QDockWidget(tr("Game List"), this)); m_game_list_frame.reset(new GameListFrame(m_game_info, m_compat_info, this)); m_game_list_frame->setObjectName("gamelist"); - m_game_grid_frame.reset(new GameGridFrame(m_game_info, this)); + m_game_grid_frame.reset(new GameGridFrame(m_game_info, m_compat_info, this)); m_game_grid_frame->setObjectName("gamegridlist"); m_elf_viewer.reset(new ElfViewer(this)); m_elf_viewer->setObjectName("elflist"); @@ -266,20 +266,26 @@ void MainWindow::CreateConnects() { &MainWindow::StartGame); connect(ui->configureAct, &QAction::triggered, this, [this]() { - auto settingsDialog = new SettingsDialog(m_physical_devices, this); + auto settingsDialog = new SettingsDialog(m_physical_devices, m_compat_info, this); connect(settingsDialog, &SettingsDialog::LanguageChanged, this, &MainWindow::OnLanguageChanged); + connect(settingsDialog, &SettingsDialog::CompatibilityChanged, this, + &MainWindow::RefreshGameTable); + settingsDialog->exec(); }); connect(ui->settingsButton, &QPushButton::clicked, this, [this]() { - auto settingsDialog = new SettingsDialog(m_physical_devices, this); + auto settingsDialog = new SettingsDialog(m_physical_devices, m_compat_info, this); connect(settingsDialog, &SettingsDialog::LanguageChanged, this, &MainWindow::OnLanguageChanged); + connect(settingsDialog, &SettingsDialog::CompatibilityChanged, this, + &MainWindow::RefreshGameTable); + settingsDialog->exec(); }); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index c9eeef107..e01b203e9 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -6,6 +6,8 @@ #include #include +#include "common/config.h" +#include "qt_gui/compatibility_info.h" #ifdef ENABLE_DISCORD_RPC #include "common/discord_rpc_handler.h" #endif @@ -55,7 +57,9 @@ QStringList languageNames = {"Arabic", const QVector languageIndexes = {21, 23, 14, 6, 18, 1, 12, 22, 2, 4, 25, 24, 29, 5, 0, 9, 15, 16, 17, 7, 26, 8, 11, 20, 3, 13, 27, 10, 19, 30, 28}; -SettingsDialog::SettingsDialog(std::span physical_devices, QWidget* parent) +SettingsDialog::SettingsDialog(std::span physical_devices, + std::shared_ptr m_compat_info, + QWidget* parent) : QDialog(parent), ui(new Ui::SettingsDialog) { ui->setupUi(this); ui->tabWidgetSettings->setUsesScrollButtons(false); @@ -141,6 +145,16 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->updaterGroupBox->setVisible(false); ui->GUIgroupBox->setMaximumSize(265, 16777215); #endif + connect(ui->updateCompatibilityButton, &QPushButton::clicked, this, + [this, parent, m_compat_info]() { + m_compat_info->UpdateCompatibilityDatabase(this, true); + emit CompatibilityChanged(); + }); + + connect(ui->enableCompatibilityCheckBox, &QCheckBox::stateChanged, this, [this](int state) { + Config::setCompatibilityEnabled(state); + emit CompatibilityChanged(); + }); } // Input TAB @@ -197,6 +211,9 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->GUIgroupBox->installEventFilter(this); ui->widgetComboBox->installEventFilter(this); ui->disableTrophycheckBox->installEventFilter(this); + ui->enableCompatibilityCheckBox->installEventFilter(this); + ui->checkCompatibilityOnStartupCheckBox->installEventFilter(this); + ui->updateCompatibilityButton->installEventFilter(this); // Input ui->hideCursorGroupBox->installEventFilter(this); @@ -291,6 +308,10 @@ void SettingsDialog::LoadValuesFromConfig() { ui->vkSyncValidationCheckBox->setChecked( toml::find_or(data, "Vulkan", "validation_sync", false)); ui->rdocCheckBox->setChecked(toml::find_or(data, "Vulkan", "rdocEnable", false)); + ui->enableCompatibilityCheckBox->setChecked( + toml::find_or(data, "General", "compatibilityEnabled", false)); + ui->checkCompatibilityOnStartupCheckBox->setChecked( + toml::find_or(data, "General", "checkCompatibilityOnStartup", false)); #ifdef ENABLE_UPDATER ui->updateCheckBox->setChecked(toml::find_or(data, "General", "autoUpdate", false)); @@ -410,6 +431,12 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("widgetComboBox"); } else if (elementName == "disableTrophycheckBox") { text = tr("disableTrophycheckBox"); + } else if (elementName == "enableCompatibilityCheckBox") { + text = tr("enableCompatibilityCheckBox"); + } else if (elementName == "checkCompatibilityOnStartupCheckBox") { + text = tr("checkCompatibilityOnStartupCheckBox"); + } else if (elementName == "updateCompatibilityButton") { + text = tr("updateCompatibilityButton"); } // Input @@ -524,6 +551,8 @@ void SettingsDialog::UpdateSettings() { Config::setRdocEnabled(ui->rdocCheckBox->isChecked()); Config::setAutoUpdate(ui->updateCheckBox->isChecked()); Config::setUpdateChannel(ui->updateComboBox->currentText().toStdString()); + Config::setCompatibilityEnabled(ui->enableCompatibilityCheckBox->isChecked()); + Config::setCheckCompatibilityOnStartup(ui->checkCompatibilityOnStartupCheckBox->isChecked()); #ifdef ENABLE_DISCORD_RPC auto* rpc = Common::Singleton::Instance(); diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index 987b35d45..892e67671 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include #include @@ -10,6 +11,7 @@ #include "common/config.h" #include "common/path_util.h" +#include "qt_gui/compatibility_info.h" namespace Ui { class SettingsDialog; @@ -18,7 +20,9 @@ class SettingsDialog; class SettingsDialog : public QDialog { Q_OBJECT public: - explicit SettingsDialog(std::span physical_devices, QWidget* parent = nullptr); + explicit SettingsDialog(std::span physical_devices, + std::shared_ptr m_compat_info, + QWidget* parent = nullptr); ~SettingsDialog(); bool eventFilter(QObject* obj, QEvent* event) override; @@ -28,6 +32,7 @@ public: signals: void LanguageChanged(const std::string& locale); + void CompatibilityChanged(); private: void LoadValuesFromConfig(); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index c3cf7e90f..98509424d 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -11,7 +11,7 @@ 0 0 - 854 + 900 834 @@ -42,18 +42,21 @@ - + true - - QFrame::Shape::NoFrame + + + 0 + 0 + - - true + + 0 - - + + true @@ -75,127 +78,25 @@ - General - - + + General + + + + + 0 + 0 + 822 + 487 + + + - - - - - - - System - - - - - - Console Language - - - - - - - - - - - - Emulator Language - - - - - - - - - - - - - - - - - - - Emulator - - - - - - - - Enable Fullscreen - - - - - - - Enable Separate Update Folder - - - - - - - Show Splash - - - - - - - Is PS4 Pro - - - - - - - Enable Discord Rich Presence - - - - - - - - - 6 - - - 0 - - - - - - - Username - - - - - - - - - - - - - - - - - - + + + 0 + + @@ -275,12 +176,129 @@ - - - - - + + + + + + System + + + + + + Console Language + + + + + + + + + + + + Emulator Language + + + + + + + + + + + + + + + + + + + Emulator + + + + + + 10 + + + + + Enable Fullscreen + + + + + + + Enable Separate Update Folder + + + + + + + Show Splash + + + + + + + Is PS4 Pro + + + + + + + Enable Discord Rich Presence + + + + + + + + + 6 + + + 0 + + + + + + + Username + + + + + + + + + + + + + + + + + + + + -1 + QLayout::SizeConstraint::SetDefaultConstraint @@ -296,7 +314,7 @@ 0 - + @@ -306,7 +324,7 @@ - 275 + 0 0 @@ -321,7 +339,7 @@ - 5 + 10 1 @@ -343,7 +361,7 @@ 0 - 75 + 0 @@ -404,8 +422,8 @@ - 197 - 28 + 0 + 0 @@ -443,7 +461,7 @@ - + @@ -559,6 +577,19 @@ + + + + + 0 + 0 + + + + Play title music + + + @@ -568,20 +599,7 @@ 0 - - - - 0 - 0 - - - - Play title music - - - - - + Qt::Orientation::Vertical @@ -591,7 +609,7 @@ 20 - 2 + 13 @@ -656,20 +674,76 @@ - - - - - - Qt::Orientation::Horizontal + + + + + + + 0 + 0 + - + - 40 - 20 + 0 + 0 - + + Game Compatibility + + + + 10 + + + 1 + + + 11 + + + + + Display Compatibility Data + + + + + + + Update Compatibility Database On Startup + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Update Compatibility Database + + + + + @@ -677,10 +751,23 @@ - - - Input - + + + + true + + + Input + + + + + 0 + 0 + 396 + 222 + + @@ -789,8 +876,8 @@ - 80 - 30 + 0 + 0 @@ -876,7 +963,7 @@ - 237 + 0 0 @@ -955,10 +1042,23 @@ - - - Graphics - + + + + true + + + Graphics + + + + + 0 + 0 + 536 + 192 + + @@ -1193,13 +1293,26 @@ - - - Paths - - + + + + true + + + Paths + + + + + 0 + 0 + 146 + 215 + + + - + @@ -1242,10 +1355,23 @@ - - - Debug - + + + + true + + + Debug + + + + + 0 + 0 + 288 + 163 + + diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index 3f861187e..cd71e083c 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - تم إنشاء الاختصار بنجاح!\n %1 + Shortcut created successfully! + تم إنشاء الاختصار بنجاح! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - !\n %1 خطأ في إنشاء الاختصار + Error creating shortcut! + خطأ في إنشاء الاختصار @@ -672,11 +672,36 @@ GUI Settings إعدادات الواجهة + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music تشغيل موسيقى العنوان + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox تشغيل موسيقى العنوان:\nإذا كانت اللعبة تدعم ذلك، قم بتمكين تشغيل موسيقى خاصة عند اختيار اللعبة في واجهة المستخدم. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox سلوك زر العودة:\nيضبط زر العودة في وحدة التحكم ليحاكي الضغط على الموضع المحدد على لوحة اللمس في PS4. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial سيريال + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 3539159e2..677789d49 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Shortcut created successfully! + Shortcut created successfully! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Error creating shortcut! + Error creating shortcut! @@ -672,11 +672,36 @@ GUI Settings GUI-Indstillinger + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Afspil titelsang + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Titelsmusikafspilning:\nHvis spillet understøtter det, aktiver speciel musik, når spillet vælges i brugergrænsefladen. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Tilbageknap Adfærd:\nIndstiller controllerens tilbageknap til at efterligne tryk på den angivne position på PS4 berøringsflade. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Seriel + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index f34402ac9..64770f6fe 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Verknüpfung erfolgreich erstellt!\n %1 + Shortcut created successfully! + Verknüpfung erfolgreich erstellt! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Fehler beim Erstellen der Verknüpfung!\n %1 + Error creating shortcut! + Fehler beim Erstellen der Verknüpfung! @@ -672,11 +672,36 @@ GUI Settings GUI-Einstellungen + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Titelmusik abspielen + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Wiedergabe der Titelmusik:\nWenn das Spiel dies unterstützt, wird beim Auswählen des Spiels in der Benutzeroberfläche spezielle Musik abgespielt. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Zurück-Button Verhalten:\nStellt die Zurück-Taste des Controllers so ein, dass sie das Antippen der angegebenen Position auf dem PS4-Touchpad emuliert. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Seriennummer + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index 65cee641a..05faa7bc9 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Shortcut created successfully! + Shortcut created successfully! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Error creating shortcut! + Error creating shortcut! @@ -672,11 +672,36 @@ GUI Settings Ρυθμίσεις GUI + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Αναπαραγωγή μουσικής τίτλου + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Αναπαραγωγή Μουσικής Τίτλων:\nΕάν το παιχνίδι το υποστηρίζει, ενεργοποιεί ειδική μουσική κατά την επιλογή του παιχνιδιού από τη διεπαφή χρήστη. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Συμπεριφορά Κουμπιού Επιστροφής:\nΟρίζει το κουμπί επιστροφής του ελεγκτή να προσομοιώνει το πάτημα της καθορισμένης θέσης στην οθόνη αφής PS4. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Σειριακός αριθμός + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index 970f686c5..7488f2170 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Shortcut created successfully! + Shortcut created successfully! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Error creating shortcut! + Error creating shortcut! @@ -672,11 +672,36 @@ GUI Settings GUI Settings + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Play title music + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1226,6 +1251,21 @@ backButtonBehaviorGroupBox Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1339,6 +1379,11 @@ Serial Serial + + + Compatibility + Compatibility + Region @@ -1374,6 +1419,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 3d1f291a6..9b8b38129 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - ¡Acceso directo creado con éxito!\n %1 + Shortcut created successfully! + ¡Acceso directo creado con éxito! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - ¡Error al crear el acceso directo!\n %1 + Error creating shortcut! + ¡Error al crear el acceso directo! @@ -672,11 +672,36 @@ GUI Settings Configuraciones de la Interfaz + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Reproducir la música de apertura + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Reproducir Música del Título:\nSi un juego lo admite, habilita la reproducción de música especial al seleccionar el juego en la interfaz gráfica. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Comportamiento del Botón Atrás:\nEstablece el botón atrás del controlador para emular el toque en la posición especificada en el touchpad del PS4. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Numero de serie + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 58de03346..66ec0b4c0 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -62,7 +62,7 @@ Select which directory you want to install to. - Select which directory you want to install to. + محلی را که می‌خواهید در آن نصب شود، انتخاب کنید. @@ -98,7 +98,7 @@ Create Shortcut - ساخت شورتکات + ایجاد میانبر @@ -113,7 +113,7 @@ Trophy Viewer - مشاهده تروفی ها + مشاهده جوایز @@ -158,32 +158,32 @@ Delete... - Delete... + حذف... Delete Game - Delete Game + حذف بازی Delete Update - Delete Update + حذف به‌روزرسانی Delete DLC - Delete DLC + حذف محتوای اضافی (DLC) Shortcut creation - سازنده شورتکات + ایجاد میانبر - Shortcut created successfully!\n %1 - شورتکات با موفقیت ساخته شد! \n %1 + Shortcut created successfully! + میانبر با موفقیت ساخته شد! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - مشکلی در هنگام ساخت شورتکات بوجود آمد!\n %1 + Error creating shortcut! + مشکلی در هنگام ساخت میانبر بوجود آمد! @@ -203,27 +203,27 @@ Game - Game + بازی requiresEnableSeparateUpdateFolder_MSG - This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + این قابلیت نیازمند فعال‌سازی گزینه تنظیمات «ایجاد پوشه جداگانه برای به‌روزرسانی» است. در صورت تمایل به استفاده از این قابلیت، لطفاً آن را فعال کنید. This game has no update to delete! - This game has no update to delete! + این بازی به‌روزرسانی‌ای برای حذف ندارد! Update - Update + به‌روزرسانی This game has no DLC to delete! - This game has no DLC to delete! + این بازی محتوای اضافی (DLC) برای حذف ندارد! @@ -233,7 +233,7 @@ Delete %1 - Delete %1 + حذف %1 @@ -331,7 +331,7 @@ List View - لیستی + نمایش لیست @@ -341,7 +341,7 @@ Elf Viewer - Elf Viewer + مشاهده گر Elf @@ -452,7 +452,7 @@ Trophy Viewer - تروفی ها + مشاهده جوایز @@ -495,7 +495,7 @@ Enable Separate Update Folder - Enable Separate Update Folder + فعال‌سازی پوشه جداگانه برای به‌روزرسانی @@ -555,7 +555,7 @@ Controller - کنترل کننده + دسته بازی @@ -585,7 +585,7 @@ Vblank Divider - Vblank Divider + تقسیم‌کننده Vblank @@ -595,7 +595,7 @@ Enable Shaders Dumping - Shaders Dumping فعال کردن + فعال‌سازی ذخیره‌سازی شیدرها @@ -625,7 +625,7 @@ Debug - Debug + دیباگ @@ -650,37 +650,62 @@ Update - بروزرسانی + به‌روزرسانی Check for Updates at Startup - بررسی بروزرسانی هنگام شروع + بررسی به‌روزرسانی‌ها در زمان راه‌اندازی Update Channel - کانال بروزرسانی + کانال به‌روزرسانی Check for Updates - به روز رسانی را بررسی کنید + بررسی به‌روزرسانی‌ها GUI Settings تنظیمات رابط کاربری + + + Disable Trophy Pop-ups + غیرفعال کردن نمایش جوایز + Play title music پخش موسیقی عنوان + + + Update Compatibility Database On Startup + به‌روزرسانی پایگاه داده سازگاری هنگام راه‌اندازی + + + + Game Compatibility + سازگاری بازی با سیستم + + + + Display Compatibility Data + نمایش داده‌های سازگاری + + + + Update Compatibility Database + به‌روزرسانی پایگاه داده سازگاری + Volume - صدا + صدا @@ -693,7 +718,7 @@ * Unsupported Vulkan Version - شما پشتیبانی نمیشود Vulkan ورژن* + شما پشتیبانی نمیشود Vulkan ورژن * @@ -841,7 +866,7 @@ Cheats / Patches for - Cheats / Patches for ا + چیت / پچ برای @@ -861,7 +886,7 @@ Version: - ورژن: + نسخه: @@ -886,7 +911,7 @@ Delete File - پاک کردن فایل + حذف فایل @@ -1149,27 +1174,27 @@ emulatorLanguageGroupBox - Emulator Language:\nSets the language of the emulator's user interface. + زبان شبیه‌ساز:\nزبان رابط کاربری شبیه‌ساز را انتخاب می‌کند. fullscreenCheckBox - Enable Full Screen:\nAutomatically puts the game window into full-screen mode.\nThis can be toggled by pressing the F11 key. + فعال‌سازی تمام صفحه:\nپنجره بازی را به‌طور خودکار به حالت تمام صفحه در می‌آورد.\nبرای تغییر این حالت می‌توانید کلید F11 را فشار دهید. separateUpdatesCheckBox - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + فعال‌سازی پوشه جداگانه برای به‌روزرسانی:\nامکان نصب به‌روزرسانی‌های بازی در یک پوشه جداگانه برای مدیریت راحت‌تر را فراهم می‌کند. showSplashCheckBox - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. + نمایش صفحه شروع:\nصفحه شروع بازی (تصویری ویژه) را هنگام بارگذاری بازی نمایش می‌دهد. ps4proCheckBox - Is PS4 Pro:\nMakes the emulator act as a PS4 PRO, which may enable special features in games that support it. + حالت PS4 Pro:\nشبیه‌ساز را به‌عنوان PS4 Pro شبیه‌سازی می‌کند که ممکن است ویژگی‌های ویژه‌ای را در بازی‌های پشتیبانی‌شده فعال کند. @@ -1179,12 +1204,12 @@ userName - Username:\nSets the PS4's account username, which may be displayed by some games. + نام کاربری:\nنام کاربری حساب PS4 را تنظیم می‌کند که ممکن است توسط برخی بازی‌ها نمایش داده شود. logTypeGroupBox - Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. + نوع لاگ:\nتنظیم می‌کند که آیا خروجی پنجره لاگ برای بهبود عملکرد همگام‌سازی شود یا خیر. این ممکن است تأثیر منفی بر شبیه‌سازی داشته باشد. @@ -1194,12 +1219,17 @@ updaterGroupBox - Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. + به‌روزرسانی:\nانتشار: نسخه‌های رسمی که هر ماه منتشر می‌شوند و ممکن است بسیار قدیمی باشند، اما پایدارتر و تست‌ شده‌تر هستند.\nشبانه: نسخه‌های توسعه‌ای که شامل جدیدترین ویژگی‌ها و اصلاحات هستند، اما ممکن است دارای اشکال باشند و کمتر پایدار باشند. GUIgroupBox - Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + پخش موسیقی عنوان:\nIدر صورتی که بازی از آن پشتیبانی کند، پخش موسیقی ویژه هنگام انتخاب بازی در رابط کاربری را فعال می‌کند. + + + + disableTrophycheckBox + غیرفعال کردن نمایش جوایز:\nنمایش اعلان‌های جوایز درون بازی را غیرفعال می‌کند. پیشرفت جوایز همچنان از طریق نمایشگر جوایز (کلیک راست روی بازی در پنجره اصلی) قابل پیگیری است.. @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox رفتار دکمه برگشت:\nدکمه برگشت کنترلر را طوری تنظیم می کند که ضربه زدن روی موقعیت مشخص شده روی صفحه لمسی PS4 را شبیه سازی کند. + + + enableCompatibilityCheckBox + نمایش داده‌های سازگاری:\nاطلاعات سازگاری بازی را به صورت جدول نمایش می‌دهد. برای دریافت اطلاعات به‌روز، گزینه "به‌روزرسانی سازگاری هنگام راه‌اندازی" را فعال کنید. + + + + checkCompatibilityOnStartupCheckBox + به‌روزرسانی سازگاری هنگام راه‌اندازی:\nبه‌طور خودکار پایگاه داده سازگاری را هنگام راه‌اندازی ShadPS4 به‌روزرسانی می‌کند. + + + + updateCompatibilityButton + به‌روزرسانی پایگاه داده سازگاری:\nپایگاه داده سازگاری را بلافاصله به‌روزرسانی می‌کند. + Never @@ -1234,7 +1279,7 @@ Touchpad Left - پد لمسی سمت چپ + صفحه لمسی سمت چپ @@ -1244,7 +1289,7 @@ Touchpad Center - مرکز تاچ پد + مرکز صفحه لمسی @@ -1254,22 +1299,22 @@ graphicsAdapterGroupBox - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. + دستگاه گرافیکی:\nدر سیستم‌های با چندین پردازنده گرافیکی، از فهرست کشویی، پردازنده گرافیکی که شبیه‌ساز از آن استفاده می‌کند را انتخاب کنید، یا گزینه "انتخاب خودکار" را انتخاب کنید تا به طور خودکار تعیین شود. resolutionLayout - Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. + عرض/ارتفاع:\nاندازه پنجره شبیه‌ساز را در هنگام راه‌اندازی تنظیم می‌کند، که در حین بازی قابل تغییر اندازه است.\nاین با وضوح داخل بازی متفاوت است. heightDivider - Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! + تقسیم‌کننده Vblank:\nمیزان فریم ریت که شبیه‌ساز با آن به‌روزرسانی می‌شود، در این عدد ضرب می‌شود. تغییر این مقدار ممکن است تأثیرات منفی داشته باشد، مانند افزایش سرعت بازی یا خراب شدن عملکردهای حیاتی بازی که انتظار تغییر آن را ندارند! dumpShadersCheckBox - Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. + فعال‌سازی ذخیره‌سازی شیدرها:\nبه‌منظور اشکال‌زدایی فنی، شیدرهای بازی را هنگام رندر شدن در یک پوشه ذخیره می‌کند. @@ -1294,7 +1339,7 @@ debugDump - Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. + فعال‌سازی ذخیره‌سازی دیباگ:\nنمادهای import و export و اطلاعات هدر فایل برنامه در حال اجرای PS4 را در یک پوشه ذخیره می‌کند. @@ -1329,6 +1374,11 @@ Serial سریال + + + Compatibility + سازگاری + Region @@ -1337,7 +1387,7 @@ Firmware - فریمور + فریم‌ور @@ -1362,7 +1412,37 @@ Never Played - Never Played + هرگز بازی نشده + + + + Compatibility is untested + سازگاری تست نشده است + + + + Game does not initialize properly / crashes the emulator + بازی به درستی راه‌اندازی نمی‌شود / شبیه‌ساز کرش می‌کند + + + + Game boots, but only displays a blank screen + بازی اجرا می‌شود، اما فقط یک صفحه خالی نمایش داده می‌شود + + + + Game displays an image but does not go past the menu + بازی تصویری نمایش می‌دهد، اما از منو فراتر نمی‌رود + + + + Game has game-breaking glitches or unplayable performance + بازی دارای اشکالات بحرانی یا عملکرد غیرقابل بازی است + + + + Game can be completed with playable performance and no major glitches + بازی با عملکرد قابل قبول و بدون اشکالات عمده قابل بازی است. @@ -1370,7 +1450,7 @@ Auto Updater - به روز رسانی خودکار + به‌روزرسانی خودکار @@ -1415,7 +1495,7 @@ Update Channel - کانال بروزرسانی + کانال به‌روزرسانی @@ -1440,7 +1520,7 @@ Check for Updates at Startup - بررسی بروزرسانی هنگام شروع + بررسی به‌روزرسانی هنگام شروع @@ -1493,4 +1573,4 @@ فایل اسکریپت به روز رسانی ایجاد نشد - \ No newline at end of file + diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index 0a7f2b250..67ea079aa 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Shortcut created successfully! + Shortcut created successfully! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Error creating shortcut! + Error creating shortcut! @@ -672,11 +672,36 @@ GUI Settings GUI-Asetukset + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Soita otsikkomusiikkia + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Soita Otsikkomusiikkia:\nJos peli tukee sitä, ota käyttöön erityisen musiikin soittaminen pelin valinnan yhteydessä käyttöliittymässä. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Takaisin-napin käyttäytyminen:\nAsettaa ohjaimen takaisin-napin jäljittelemään kosketusta PS4:n kosketuslevyn määritettyyn kohtaan. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Sarjanumero + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index fad90622a..c092580a8 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Raccourci créé avec succès !\n %1 + Shortcut created successfully! + Raccourci créé avec succès ! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Erreur lors de la création du raccourci !\n %1 + Error creating shortcut! + Erreur lors de la création du raccourci ! @@ -672,11 +672,36 @@ GUI Settings Paramètres de l'interface + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Lire la musique du titre + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Jouer de la musique de titre:\nSi le jeu le prend en charge, cela active la musique spéciale lorsque vous sélectionnez le jeu dans l'interface utilisateur. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Comportement du bouton retour:\nDéfinit le bouton de retour de la manette pour imiter le toucher de la position spécifiée sur le pavé tactile PS4. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Série + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Jamais joué + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 937e3f188..7e60001f6 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Parancsikon sikeresen létrehozva!\n %1 + Shortcut created successfully! + Parancsikon sikeresen létrehozva! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Hiba a parancsikon létrehozásával!\n %1 + Error creating shortcut! + Hiba a parancsikon létrehozásával! @@ -672,11 +672,36 @@ GUI Settings GUI Beállítások + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Címzene lejátszása + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Játék címzene lejátszása:\nHa a játék támogatja, engedélyezze egy speciális zene lejátszását, amikor a játékot kiválasztja a GUI-ban. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Vissza gomb viselkedés:\nBeállítja a vezérlő vissza gombját, hogy utánozza a PS4 érintőpadján megadott pozíció megérintését. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Sorozatszám + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate @@ -1493,4 +1573,4 @@ A frissítési szkript fájl létrehozása nem sikerült - + \ No newline at end of file diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index 80873daa9..31f377341 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Shortcut created successfully! + Shortcut created successfully! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Error creating shortcut! + Error creating shortcut! @@ -672,11 +672,36 @@ GUI Settings Pengaturan GUI + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Putar musik judul + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Putar Musik Judul Permainan:\nJika permainan mendukungnya, aktifkan pemutaran musik khusus saat memilih permainan di GUI. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Perilaku Tombol Kembali:\nMengatur tombol kembali pada pengontrol untuk meniru ketukan di posisi yang ditentukan di touchpad PS4. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Serial + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index 9094a7ed5..8dc0817f1 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Scorciatoia creata con successo!\n %1 + Shortcut created successfully! + Scorciatoia creata con successo! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Errore nella creazione della scorciatoia!\n %1 + Error creating shortcut! + Errore nella creazione della scorciatoia! @@ -208,7 +208,7 @@ requiresEnableSeparateUpdateFolder_MSG - This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + Questa feature richiede che venga attivata l'opzione "Abilita Cartella Aggiornamenti Separata" per poter funzionare, per favore abilitala. @@ -495,7 +495,7 @@ Enable Separate Update Folder - Abilità Cartella Aggiornamenti Separata + Abilita Cartella Aggiornamenti Separata @@ -672,11 +672,36 @@ GUI Settings Impostazioni GUI + + + Disable Trophy Pop-ups + Disabilita Notifica Trofei + Play title music Riproduci musica del titolo + + + Update Compatibility Database On Startup + Aggiorna Database Compatibilità all'Avvio + + + + Game Compatibility + Compatibilità Gioco + + + + Display Compatibility Data + Mostra Dati Compatibilità + + + + Update Compatibility Database + Aggiorna Database Compatibilità + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Riproduci Musica del Titolo:\nSe un gioco lo supporta, attiva la riproduzione di musica speciale quando selezioni il gioco nell'interfaccia grafica. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Comportamento del pulsante Indietro:\nImposta il pulsante Indietro del controller per emulare il tocco sulla posizione specificata sul touchpad PS4. + + + enableCompatibilityCheckBox + Mostra Dati Compatibilità:\nMostra informazioni sulla compatibilità del gioco nella visualizzazione lista. Abilita "Aggiorna Compatiblità all'Avvio" per ottenere informazioni aggiornate. + + + + checkCompatibilityOnStartupCheckBox + Aggiorna Compatibilità all'Avvio:\nAggiorna automaticamente il database della compatibilità quando si avvia shadps4. + + + + updateCompatibilityButton + Aggiorna Database Compatibilità:\nAggiorna immediatamente il database di compatibilità. + Never @@ -1329,6 +1374,11 @@ Serial Seriale + + + Compatibility + Compatibilità + Region @@ -1362,7 +1412,37 @@ Never Played - Never Played + Mai Giocato + + + + Compatibility is untested + Nessuna informazione sulla compatibilità + + + + Game does not initialize properly / crashes the emulator + Il gioco non si avvia in modo corretto / forza chiusura dell'emulatore + + + + Game boots, but only displays a blank screen + Il gioco si avvia, ma mostra solo una schermata nera + + + + Game displays an image but does not go past the menu + Il gioco mostra immagini ma non va oltre il menu + + + + Game has game-breaking glitches or unplayable performance + Il gioco ha problemi gravi di emulazione oppure framerate troppo basso + + + + Game can be completed with playable performance and no major glitches + Il gioco può essere completato con buone prestazioni e senza problemi gravi diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index ad1f383fe..43fb37c2c 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - ショートカットが正常に作成されました!\n %1 + Shortcut created successfully! + ショートカットが正常に作成されました! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - ショートカットの作成に失敗しました!\n %1 + Error creating shortcut! + ショートカットの作成に失敗しました! @@ -672,11 +672,36 @@ GUI Settings GUI設定 + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music タイトル音楽を再生する + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox タイトルミュージックを再生:\nゲームがそれをサポートしている場合、GUIでゲームを選択したときに特別な音楽を再生することを有効にします。 + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox 戻るボタンの動作:\nコントローラーの戻るボタンを、PS4のタッチパッドの指定された位置をタッチするように設定します。 + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial シリアル + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index a528db295..ffaa8404f 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Shortcut created successfully! + Shortcut created successfully! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Error creating shortcut! + Error creating shortcut! @@ -672,11 +672,36 @@ GUI Settings GUI Settings + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Play title music + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Serial + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 4a2820399..5cf4d0e6b 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Shortcut created successfully! + Shortcut created successfully! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Error creating shortcut! + Error creating shortcut! @@ -672,11 +672,36 @@ GUI Settings GUI Nustatymai + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Groti antraštės muziką + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Groti antraščių muziką:\nJei žaidimas tai palaiko, įjungia specialios muzikos grojimą, kai pasirinkite žaidimą GUI. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Atgal mygtuko elgesys:\nNustato valdiklio atgal mygtuką imituoti paspaudimą nurodytoje vietoje PS4 jutiklinėje plokštėje. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Serijinis numeris + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index 028646740..ecc323879 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Snarvei opprettet!\n %1 + Shortcut created successfully! + Snarvei opprettet! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Feil ved opprettelse av snarvei!\n %1 + Error creating shortcut! + Feil ved opprettelse av snarvei! @@ -672,11 +672,36 @@ GUI Settings GUI-Innstillinger + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Spill tittelmusikk + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Spille tittelmusikk:\nHvis et spill støtter det, så aktiveres det spesiell musikk når du velger spillet i menyen. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Atferd for tilbaketasten:\nSetter tilbaketasten på kontrolleren til å imitere et trykk på den angitte posisjonen på PS4s berøringsplate. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Serienummer + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate @@ -1493,4 +1573,4 @@ Kunne ikke opprette oppdateringsskriptfilen - + \ No newline at end of file diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index b66cb94e4..eb49c83ab 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Shortcut created successfully! + Shortcut created successfully! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Error creating shortcut! + Error creating shortcut! @@ -672,11 +672,36 @@ GUI Settings GUI-Instellingen + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Titelmuziek afspelen + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Speel titelsong:\nAls een game dit ondersteunt, wordt speciale muziek afgespeeld wanneer je het spel in de GUI selecteert. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Gedrag van de terugknop:\nStelt de terugknop van de controller in om een aanraking op de opgegeven positie op de PS4-touchpad na te bootsen. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Serienummer + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 8236cf720..cb9e7987d 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Utworzenie skrótu zakończone pomyślnie!\n %1 + Shortcut created successfully! + Utworzenie skrótu zakończone pomyślnie! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Utworzenie skrótu zakończone niepowodzeniem!\n %1 + Error creating shortcut! + Utworzenie skrótu zakończone niepowodzeniem! @@ -672,11 +672,36 @@ GUI Settings Ustawienia Interfejsu + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Odtwórz muzykę tytułową + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Odtwórz muzykę tytułową:\nJeśli gra to obsługuje, aktywuje odtwarzanie specjalnej muzyki podczas wybierania gry w GUI. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Zachowanie przycisku Wstecz:\nUstawia przycisk Wstecz kontrolera tak, aby emulował dotknięcie określonego miejsca na panelu dotykowym PS4. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Numer seryjny + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 5faccf6c5..a668c61d1 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Atalho criado com sucesso!\n %1 + Shortcut created successfully! + Atalho criado com sucesso! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Erro ao criar atalho!\n %1 + Error creating shortcut! + Erro ao criar atalho! @@ -672,11 +672,36 @@ GUI Settings Configurações da Interface + + + Disable Trophy Pop-ups + Desabilitar Pop-ups dos Troféus + Play title music Reproduzir música de abertura + + + Update Compatibility Database On Startup + Atualizar Compatibilidade ao Inicializar + + + + Game Compatibility + Compatibilidade dos Jogos + + + + Display Compatibility Data + Exibir Dados de Compatibilidade + + + + Update Compatibility Database + Atualizar Lista de Compatibilidade + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Reproduzir música de abertura:\nSe o jogo suportar, ativa a reprodução de uma música especial ao selecionar o jogo na interface do menu. + + + disableTrophycheckBox + Desabilitar pop-ups dos troféus:\nDesabilite notificações de troféus no jogo. O progresso do troféu ainda pode ser rastreado usando o Trophy Viewer (clique com o botão direito do mouse no jogo na janela principal). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Comportamento do botão Voltar:\nDefine o botão Voltar do controle para emular o toque na posição especificada no touchpad do PS4. + + + enableCompatibilityCheckBox + Exibir Dados de Compatibilidade:\nExibe informações de compatibilidade dos jogos na janela principal.\nHabilitar "Atualizar Compatibilidade ao Inicializar" para obter informações atualizadas. + + + + checkCompatibilityOnStartupCheckBox + Atualizar Compatibilidade ao inicializar:\nAtualiza automaticamente o banco de dados de compatibilidade quando o SHADPS4 é iniciado. + + + + updateCompatibilityButton + Atualizar Lista de Compatibilidade:\nAtualizar imediatamente o banco de dados de compatibilidade. + Never @@ -1329,6 +1374,11 @@ Serial Serial + + + Compatibility + Compatibilidade + Region @@ -1364,6 +1414,36 @@ Never Played Nunca jogado + + + Compatibility is untested + Compatibilidade não testada + + + + Game does not initialize properly / crashes the emulator + Jogo não inicializa corretamente / trava o emulador + + + + Game boots, but only displays a blank screen + O jogo inicializa, mas exibe apenas uma tela vazia + + + + Game displays an image but does not go past the menu + Jogo exibe imagem mas não passa do menu + + + + Game has game-breaking glitches or unplayable performance + O jogo tem falhas que interrompem o jogo ou desempenho injogável + + + + Game can be completed with playable performance and no major glitches + O jogo pode ser concluído com desempenho jogável e sem grandes falhas + CheckUpdate diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 2439e69e2..ae7e2efcb 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Shortcut created successfully! + Shortcut created successfully! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Error creating shortcut! + Error creating shortcut! @@ -672,11 +672,36 @@ GUI Settings Setări GUI + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Redă muzica titlului + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Redă muzica titlului:\nDacă un joc o suportă, activează redarea muzicii speciale când selectezi jocul în GUI. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Comportamentul butonului înapoi:\nSetează butonul înapoi al controlerului să imite atingerea poziției specificate pe touchpad-ul PS4. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Serie + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index ccee34517..0349dfce7 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Ярлык создан успешно!\n %1 + Shortcut created successfully! + Ярлык создан успешно! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Ошибка создания ярлыка!\n %1 + Error creating shortcut! + Ошибка создания ярлыка! @@ -672,11 +672,36 @@ GUI Settings Интерфейс + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Играть заглавную музыку + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Играть заглавную музыку:\nВключает воспроизведение специальной музыки при выборе игры в списке, если она это поддерживает. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Поведение кнопки «Назад»:\nНастраивает кнопку «Назад» контроллера на эмуляцию нажатия на указанную область на сенсорной панели контроллера PS4. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Серийный номер + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index 4a02298e8..c3280e0ea 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Shkurtorja u krijua me sukses!\n %1 + Shortcut created successfully! + Shkurtorja u krijua me sukses! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Gabim në krijimin e shkurtores!\n %1 + Error creating shortcut! + Gabim në krijimin e shkurtores! @@ -672,11 +672,36 @@ GUI Settings Cilësimet e GUI + + + Disable Trophy Pop-ups + Çaktivizo njoftimet për Trofetë + Play title music Luaj muzikën e titullit + + + Update Compatibility Database On Startup + Përditëso bazën e të dhënave të përputhshmërisë gjatë nisjes + + + + Game Compatibility + Përputhshmëria e lojës + + + + Display Compatibility Data + Shfaq të dhënat e përputhshmërisë + + + + Update Compatibility Database + Përditëso bazën e të dhënave të përputhshmërisë + Volume @@ -841,7 +866,7 @@ Cheats / Patches for - Cheats / Patches for + Mashtrime / Arna për @@ -1159,7 +1184,7 @@ separateUpdatesCheckBox - Aktivizo dosjen e ndarë të përditësimit:\nAktivizon instalimin e përditësimeve të lojërave në dosje të veçanta për menaxhim më të lehtë. + Aktivizo dosjen e ndarë të përditësimit:\nAktivizon instalimin e përditësimeve të lojërave në dosje të veçanta për menaxhim më të lehtë.\nKjo mund të krijohet manualisht duke shtuar përditësimin e shpaketuar në dosjen e lojës me emrin "CUSA00000-UPDATE" ku ID-ja CUSA përputhet me ID-në e lojës. @@ -1201,6 +1226,11 @@ GUIgroupBox Luaj muzikën e titullit:\nNëse një lojë e mbështet, aktivizohet luajtja e muzikës të veçantë kur të zgjidhësh lojën në GUI. + + + disableTrophycheckBox + Çaktivizo njoftimet për Trofetë:\nÇaktivizo njoftimet për trofetë gjatë lojës. Përparimi i trofeve mund të ndiqet duke përdorur Shikuesin e Trofeve (kliko me të djathtën mbi lojën në dritaren kryesore). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Sjellja e butonit mbrapa:\nLejon të përcaktohet se në cilën pjesë të tastierës prekëse do të imitojë një prekje butoni mprapa. + + + enableCompatibilityCheckBox + Shfaq të dhënat e përputhshmërisë:\nShfaq informacionin e përputhshmërisë së lojës në formë tabele. Aktivizo 'Përditëso përputhshmërinë gjatë nisjes' për të marrë informacion të përditësuar. + + + + checkCompatibilityOnStartupCheckBox + Përditëso përputhshmërinë gjatë nisjes:\nPërditëson automatikisht bazën e të dhënave të përputhshmërisë kur shadPS4 niset. + + + + updateCompatibilityButton + Përditëso bazën e të dhënave të përputhshmërisë:\nPërditëso menjëherë bazën e të dhënave të përputhshmërisë. + Never @@ -1329,6 +1374,11 @@ Serial Seriku + + + Compatibility + Përputhshmëria + Region @@ -1362,7 +1412,37 @@ Never Played - Never Played + Nuk është luajtur kurrë + + + + Compatibility is untested + Përputhshmëria nuk është e testuar + + + + Game does not initialize properly / crashes the emulator + Loja nuk niset siç duhet / rrëzon emulatorin + + + + Game boots, but only displays a blank screen + Loja niset, por shfaq vetëm një ekran të zbrazët + + + + Game displays an image but does not go past the menu + Loja shfaq një imazh, por nuk kalon përtej menysë + + + + Game has game-breaking glitches or unplayable performance + Loja ka probleme kritike ose performancë të papërshtatshme për lojë + + + + Game can be completed with playable performance and no major glitches + Loja mund të përfundohet me performancë të luajtshme dhe pa probleme të mëdha diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 4c77bc16a..4d644ecfe 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Kısayol başarıyla oluşturuldu!\n %1 + Shortcut created successfully! + Kısayol başarıyla oluşturuldu! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Kısayol oluşturulurken hata oluştu!\n %1 + Error creating shortcut! + Kısayol oluşturulurken hata oluştu! @@ -672,11 +672,36 @@ GUI Settings GUI Ayarları + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Başlık müziğini çal + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Başlık Müziklerini Çal:\nEğer bir oyun bunu destekliyorsa, GUI'de oyunu seçtiğinizde özel müziklerin çalmasını etkinleştirir. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Geri düğmesi davranışı:\nKontrol cihazındaki geri düğmesini, PS4'ün dokunmatik panelindeki belirlenen noktaya dokunmak için ayarlar. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Seri Numarası + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate @@ -1493,4 +1573,4 @@ Güncelleme betiği dosyası oluşturulamadı - + \ No newline at end of file diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 805fff151..66cbf2bd9 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Ярлик створений успішно!\n %1 + Shortcut created successfully! + Ярлик створений успішно! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Помилка при створенні ярлика!\n %1 + Error creating shortcut! + Помилка при створенні ярлика! @@ -672,11 +672,36 @@ GUI Settings Інтерфейс + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Програвати заголовну музику + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Грати заголовну музику:\nВмикає відтворення спеціальної музики під час вибору гри в списку, якщо вона це підтримує. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Поведінка кнопки «Назад»:\nНалаштовує кнопку «Назад» контролера на емуляцію натискання на зазначену область на сенсорній панелі контролера PS4. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Серійний номер + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 1ac3d042d..9441d1697 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Shortcut created successfully! + Shortcut created successfully! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Error creating shortcut! + Error creating shortcut! @@ -672,11 +672,36 @@ GUI Settings Cài đặt GUI + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Phát nhạc tiêu đề + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox Phát nhạc tiêu đề trò chơi:\nNếu một trò chơi hỗ trợ điều này, hãy kích hoạt phát nhạc đặc biệt khi bạn chọn trò chơi trong GUI. + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox Hành vi nút quay lại:\nĐặt nút quay lại của tay cầm để mô phỏng việc chạm vào vị trí đã chỉ định trên touchpad của PS4. + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial Số seri + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 19fb8edff..92071c5b2 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - 创建快捷方式成功!\n %1 + Shortcut created successfully! + 创建快捷方式成功! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - 创建快捷方式出错!\n %1 + Error creating shortcut! + 创建快捷方式出错! @@ -672,11 +672,36 @@ GUI Settings 界面设置 + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music 播放标题音乐 + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox 播放标题音乐:\n如果游戏支持,在图形界面选择游戏时启用播放特殊音乐。 + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox 返回按钮行为:\n设置控制器的返回按钮以模拟在 PS4 触控板上指定位置的点击。 + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial 序列号 + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate @@ -1493,4 +1573,4 @@ 无法创建更新脚本文件 - + \ No newline at end of file diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index fbd6d624d..be7cd69ec 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -182,8 +182,8 @@ - Shortcut created successfully!\n %1 - Shortcut created successfully!\n %1 + Shortcut created successfully! + Shortcut created successfully! @@ -192,8 +192,8 @@ - Error creating shortcut!\n %1 - Error creating shortcut!\n %1 + Error creating shortcut! + Error creating shortcut! @@ -672,11 +672,36 @@ GUI Settings 介面設置 + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music 播放標題音樂 + + + Update Compatibility Database On Startup + Update Compatibility Database On Startup + + + + Game Compatibility + Game Compatibility + + + + Display Compatibility Data + Display Compatibility Data + + + + Update Compatibility Database + Update Compatibility Database + Volume @@ -1201,6 +1226,11 @@ GUIgroupBox 播放標題音樂:\n如果遊戲支持,啟用在GUI中選擇遊戲時播放特殊音樂。 + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + hideCursorGroupBox @@ -1216,6 +1246,21 @@ backButtonBehaviorGroupBox 返回按鈕行為:\n設定控制器的返回按鈕模擬在 PS4 觸控板上指定位置的觸碰。 + + + enableCompatibilityCheckBox + Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + + + + checkCompatibilityOnStartupCheckBox + Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + + + + updateCompatibilityButton + Update Compatibility Database:\nImmediately update the compatibility database. + Never @@ -1329,6 +1374,11 @@ Serial 序號 + + + Compatibility + Compatibility + Region @@ -1364,6 +1414,36 @@ Never Played Never Played + + + Compatibility is untested + Compatibility is untested + + + + Game does not initialize properly / crashes the emulator + Game does not initialize properly / crashes the emulator + + + + Game boots, but only displays a blank screen + Game boots, but only displays a blank screen + + + + Game displays an image but does not go past the menu + Game displays an image but does not go past the menu + + + + Game has game-breaking glitches or unplayable performance + Game has game-breaking glitches or unplayable performance + + + + Game can be completed with playable performance and no major glitches + Game can be completed with playable performance and no major glitches + CheckUpdate diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index e545e8e36..0ce9eea7c 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later + #include #include #include diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index e5d4f3077..2946edab3 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -168,22 +168,6 @@ Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, return texture.is_integer ? ctx.OpBitcast(ctx.F32[4], texels) : texels; } -Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, - const IR::Value& offset, Id ms) { - const auto& texture = ctx.images[handle & 0xFFFF]; - const Id image = ctx.OpLoad(texture.image_type, texture.id); - const Id result_type = texture.data_types->Get(4); - ImageOperands operands; - operands.AddOffset(ctx, offset); - operands.Add(spv::ImageOperandsMask::Lod, lod); - operands.Add(spv::ImageOperandsMask::Sample, ms); - const Id texel = - texture.is_storage - ? ctx.OpImageRead(result_type, image, coords, operands.mask, operands.operands) - : ctx.OpImageFetch(result_type, image, coords, operands.mask, operands.operands); - return texture.is_integer ? ctx.OpBitcast(ctx.F32[4], texel) : texel; -} - Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod, bool has_mips) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); @@ -236,15 +220,34 @@ Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id return texture.is_integer ? ctx.OpBitcast(ctx.F32[4], sample) : sample; } -Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id lod) { - UNREACHABLE_MSG("SPIR-V Instruction"); -} - -void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id color) { +Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id ms) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id color_type = texture.data_types->Get(4); ImageOperands operands; + operands.Add(spv::ImageOperandsMask::Sample, ms); + Id texel; + if (!texture.is_storage) { + operands.Add(spv::ImageOperandsMask::Lod, lod); + texel = ctx.OpImageFetch(color_type, image, coords, operands.mask, operands.operands); + } else { + if (ctx.profile.supports_image_load_store_lod) { + operands.Add(spv::ImageOperandsMask::Lod, lod); + } else if (Sirit::ValidId(lod)) { + LOG_WARNING(Render, "Image read with LOD not supported by driver"); + } + texel = ctx.OpImageRead(color_type, image, coords, operands.mask, operands.operands); + } + return !texture.is_integer ? ctx.OpBitcast(ctx.U32[4], texel) : texel; +} + +void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id ms, + Id color) { + const auto& texture = ctx.images[handle & 0xFFFF]; + const Id image = ctx.OpLoad(texture.image_type, texture.id); + const Id color_type = texture.data_types->Get(4); + ImageOperands operands; + operands.Add(spv::ImageOperandsMask::Sample, ms); if (ctx.profile.supports_image_load_store_lod) { operands.Add(spv::ImageOperandsMask::Lod, lod); } else if (Sirit::ValidId(lod)) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index f71c61af6..2f606eb45 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -395,14 +395,13 @@ Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, const IR::Value& offset); Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, const IR::Value& offset, Id dref); -Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, - const IR::Value& offset, Id ms); Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod, bool skip_mips); Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords); Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives_dx, Id derivatives_dy, const IR::Value& offset, const IR::Value& lod_clamp); -Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id lod); -void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id color); +Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id ms); +void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id ms, + Id color); Id EmitImageAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value); Id EmitImageAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_quad_rect.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_quad_rect.cpp new file mode 100644 index 000000000..74a807c57 --- /dev/null +++ b/src/shader_recompiler/backend/spirv/emit_spirv_quad_rect.cpp @@ -0,0 +1,329 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "shader_recompiler/backend/spirv/emit_spirv_quad_rect.h" +#include "shader_recompiler/runtime_info.h" + +namespace Shader::Backend::SPIRV { + +using Sirit::Id; + +constexpr u32 SPIRV_VERSION_1_5 = 0x00010500; + +struct QuadRectListEmitter : public Sirit::Module { + explicit QuadRectListEmitter(const FragmentRuntimeInfo& fs_info_) + : Sirit::Module{SPIRV_VERSION_1_5}, fs_info{fs_info_}, inputs{fs_info_.num_inputs}, + outputs{fs_info_.num_inputs} { + void_id = TypeVoid(); + bool_id = TypeBool(); + float_id = TypeFloat(32); + uint_id = TypeUInt(32U); + int_id = TypeInt(32U, true); + bvec2_id = TypeVector(bool_id, 2); + vec2_id = TypeVector(float_id, 2); + vec3_id = TypeVector(float_id, 3); + vec4_id = TypeVector(float_id, 4); + + float_one = Constant(float_id, 1.0f); + float_min_one = Constant(float_id, -1.0f); + int_zero = Constant(int_id, 0); + + const Id float_arr{TypeArray(float_id, Constant(uint_id, 1U))}; + gl_per_vertex_type = TypeStruct(vec4_id, float_id, float_arr, float_arr); + Decorate(gl_per_vertex_type, spv::Decoration::Block); + MemberDecorate(gl_per_vertex_type, 0U, spv::Decoration::BuiltIn, + static_cast(spv::BuiltIn::Position)); + MemberDecorate(gl_per_vertex_type, 1U, spv::Decoration::BuiltIn, + static_cast(spv::BuiltIn::PointSize)); + MemberDecorate(gl_per_vertex_type, 2U, spv::Decoration::BuiltIn, + static_cast(spv::BuiltIn::ClipDistance)); + MemberDecorate(gl_per_vertex_type, 3U, spv::Decoration::BuiltIn, + static_cast(spv::BuiltIn::CullDistance)); + } + + /// Emits tessellation control shader for interpolating the 4th vertex of rectange primitive + void EmitRectListTCS() { + DefineEntry(spv::ExecutionModel::TessellationControl); + + // Set passthrough tessellation factors + const Id output_float_id{TypePointer(spv::StorageClass::Output, float_id)}; + for (int i = 0; i < 4; i++) { + const Id ptr{OpAccessChain(output_float_id, gl_tess_level_outer, Int(i))}; + OpStore(ptr, float_one); + } + for (int i = 0; i < 2; i++) { + const Id ptr{OpAccessChain(output_float_id, gl_tess_level_inner, Int(i))}; + OpStore(ptr, float_one); + } + + const Id input_vec4{TypePointer(spv::StorageClass::Input, vec4_id)}; + const Id output_vec4{TypePointer(spv::StorageClass::Output, vec4_id)}; + + // Emit interpolation block of the 4th vertex in rect. + // Load positions + std::array pos; + for (int i = 0; i < 3; i++) { + pos[i] = OpLoad(vec4_id, OpAccessChain(input_vec4, gl_in, Int(i), int_zero)); + } + + std::array point_coord_equal; + for (int i = 0; i < 3; i++) { + // point_coord_equal[i] = equal(gl_in[i].gl_Position.xy, gl_in[(i + 1) % + // 3].gl_Position.xy); + const Id pos_l_xy{OpVectorShuffle(vec2_id, pos[i], pos[i], 0, 1)}; + const Id pos_r_xy{OpVectorShuffle(vec2_id, pos[(i + 1) % 3], pos[(i + 1) % 3], 0, 1)}; + point_coord_equal[i] = OpFOrdEqual(bvec2_id, pos_l_xy, pos_r_xy); + } + + std::array bary_coord; + std::array is_edge_vertex; + for (int i = 0; i < 3; i++) { + // bool xy_equal = point_coord_equal[i].x && point_coord_equal[(i + 2) % 3].y; + const Id xy_equal{ + OpLogicalAnd(bool_id, OpCompositeExtract(bool_id, point_coord_equal[i], 0), + OpCompositeExtract(bool_id, point_coord_equal[(i + 2) % 3], 1))}; + // bool yx_equal = point_coord_equal[i].y && point_coord_equal[(i + 2) % 3].x; + const Id yx_equal{ + OpLogicalAnd(bool_id, OpCompositeExtract(bool_id, point_coord_equal[i], 1), + OpCompositeExtract(bool_id, point_coord_equal[(i + 2) % 3], 0))}; + // bary_coord[i] = (xy_equal || yx_equal) ? -1.f : 1.f; + is_edge_vertex[i] = OpLogicalOr(bool_id, xy_equal, yx_equal); + bary_coord[i] = OpSelect(float_id, is_edge_vertex[i], float_min_one, float_one); + } + + const auto interpolate = [&](Id v0, Id v1, Id v2) { + // return v0 * bary_coord.x + v1 * bary_coord.y + v2 * bary_coord.z; + const Id p0{OpVectorTimesScalar(vec4_id, v0, bary_coord[0])}; + const Id p1{OpVectorTimesScalar(vec4_id, v1, bary_coord[1])}; + const Id p2{OpVectorTimesScalar(vec4_id, v2, bary_coord[2])}; + return OpFAdd(vec4_id, p0, OpFAdd(vec4_id, p1, p2)); + }; + + // int vertex_index_id = is_edge_vertex[1] ? 1 : (is_edge_vertex[2] ? 2 : 0); + Id vertex_index{OpSelect(int_id, is_edge_vertex[2], Int(2), Int(0))}; + vertex_index = OpSelect(int_id, is_edge_vertex[1], Int(1), vertex_index); + + // int index = (vertex_index_id + gl_InvocationID) % 3; + const Id invocation_id{OpLoad(int_id, gl_invocation_id)}; + const Id invocation_3{OpIEqual(bool_id, invocation_id, Int(3))}; + const Id index{OpSMod(int_id, OpIAdd(int_id, vertex_index, invocation_id), Int(3))}; + + // gl_out[gl_InvocationID].gl_Position = gl_InvocationID == 3 ? pos3 : + // gl_in[index].gl_Position; + const Id pos3{interpolate(pos[0], pos[1], pos[2])}; + const Id in_ptr{OpAccessChain(input_vec4, gl_in, index, Int(0))}; + const Id position{OpSelect(vec4_id, invocation_3, pos3, OpLoad(vec4_id, in_ptr))}; + OpStore(OpAccessChain(output_vec4, gl_out, invocation_id, Int(0)), position); + + // Set attributes + for (int i = 0; i < inputs.size(); i++) { + // vec4 in_paramN3 = interpolate(bary_coord, in_paramN[0], in_paramN[1], in_paramN[2]); + const Id v0{OpLoad(vec4_id, OpAccessChain(input_vec4, inputs[i], Int(0)))}; + const Id v1{OpLoad(vec4_id, OpAccessChain(input_vec4, inputs[i], Int(1)))}; + const Id v2{OpLoad(vec4_id, OpAccessChain(input_vec4, inputs[i], Int(2)))}; + const Id in_param3{interpolate(v0, v1, v2)}; + // out_paramN[gl_InvocationID] = gl_InvocationID == 3 ? in_paramN3 : in_paramN[index]; + const Id in_param{OpLoad(vec4_id, OpAccessChain(input_vec4, inputs[i], index))}; + const Id out_param{OpSelect(vec4_id, invocation_3, in_param3, in_param)}; + OpStore(OpAccessChain(output_vec4, outputs[i], invocation_id), out_param); + } + + OpReturn(); + OpFunctionEnd(); + } + + /// Emits a passthrough quad tessellation control shader that outputs 4 control points. + void EmitQuadListTCS() { + DefineEntry(spv::ExecutionModel::TessellationControl); + const Id array_type{TypeArray(int_id, Int(4))}; + const Id values{ConstantComposite(array_type, Int(1), Int(2), Int(0), Int(3))}; + const Id indices{AddLocalVariable(TypePointer(spv::StorageClass::Function, array_type), + spv::StorageClass::Function, values)}; + + // Set passthrough tessellation factors + const Id output_float{TypePointer(spv::StorageClass::Output, float_id)}; + for (int i = 0; i < 4; i++) { + const Id ptr{OpAccessChain(output_float, gl_tess_level_outer, Int(i))}; + OpStore(ptr, float_one); + } + for (int i = 0; i < 2; i++) { + const Id ptr{OpAccessChain(output_float, gl_tess_level_inner, Int(i))}; + OpStore(ptr, float_one); + } + + const Id input_vec4{TypePointer(spv::StorageClass::Input, vec4_id)}; + const Id output_vec4{TypePointer(spv::StorageClass::Output, vec4_id)}; + const Id func_int{TypePointer(spv::StorageClass::Function, int_id)}; + const Id invocation_id{OpLoad(int_id, gl_invocation_id)}; + const Id index{OpLoad(int_id, OpAccessChain(func_int, indices, invocation_id))}; + + // gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; + const Id in_position{OpLoad(vec4_id, OpAccessChain(input_vec4, gl_in, index, Int(0)))}; + OpStore(OpAccessChain(output_vec4, gl_out, invocation_id, Int(0)), in_position); + + for (int i = 0; i < inputs.size(); i++) { + // out_paramN[gl_InvocationID] = in_paramN[gl_InvocationID]; + const Id in_param{OpLoad(vec4_id, OpAccessChain(input_vec4, inputs[i], index))}; + OpStore(OpAccessChain(output_vec4, outputs[i], invocation_id), in_param); + } + + OpReturn(); + OpFunctionEnd(); + } + + /// Emits a passthrough quad tessellation evaluation shader that outputs 4 control points. + void EmitPassthroughTES() { + DefineEntry(spv::ExecutionModel::TessellationEvaluation); + + // const int index = int(gl_TessCoord.y) * 2 + int(gl_TessCoord.x); + const Id input_float{TypePointer(spv::StorageClass::Input, float_id)}; + const Id tess_coord_x{OpLoad(float_id, OpAccessChain(input_float, gl_tess_coord, Int(0)))}; + const Id tess_coord_y{OpLoad(float_id, OpAccessChain(input_float, gl_tess_coord, Int(1)))}; + const Id index{OpIAdd(int_id, OpIMul(int_id, OpConvertFToS(int_id, tess_coord_y), Int(2)), + OpConvertFToS(int_id, tess_coord_x))}; + + // gl_Position = gl_in[index].gl_Position; + const Id input_vec4{TypePointer(spv::StorageClass::Input, vec4_id)}; + const Id output_vec4{TypePointer(spv::StorageClass::Output, vec4_id)}; + const Id position{OpLoad(vec4_id, OpAccessChain(input_vec4, gl_in, index, Int(0)))}; + OpStore(OpAccessChain(output_vec4, gl_per_vertex, Int(0)), position); + + // out_paramN = in_paramN[index]; + for (int i = 0; i < inputs.size(); i++) { + const Id param{OpLoad(vec4_id, OpAccessChain(input_vec4, inputs[i], index))}; + OpStore(outputs[i], param); + } + + OpReturn(); + OpFunctionEnd(); + } + +private: + Id Int(s32 value) { + return Constant(int_id, value); + } + + Id AddInput(Id type) { + const Id input{AddGlobalVariable(TypePointer(spv::StorageClass::Input, type), + spv::StorageClass::Input)}; + interfaces.push_back(input); + return input; + } + + Id AddOutput(Id type) { + const Id output{AddGlobalVariable(TypePointer(spv::StorageClass::Output, type), + spv::StorageClass::Output)}; + interfaces.push_back(output); + return output; + } + + void DefineEntry(spv::ExecutionModel model) { + AddCapability(spv::Capability::Shader); + AddCapability(spv::Capability::Tessellation); + const Id void_function{TypeFunction(void_id)}; + main = OpFunction(void_id, spv::FunctionControlMask::MaskNone, void_function); + if (model == spv::ExecutionModel::TessellationControl) { + AddExecutionMode(main, spv::ExecutionMode::OutputVertices, 4U); + } else { + AddExecutionMode(main, spv::ExecutionMode::Quads); + AddExecutionMode(main, spv::ExecutionMode::SpacingEqual); + AddExecutionMode(main, spv::ExecutionMode::VertexOrderCw); + } + DefineInputs(model); + DefineOutputs(model); + AddEntryPoint(model, main, "main", interfaces); + AddLabel(OpLabel()); + } + + void DefineOutputs(spv::ExecutionModel model) { + if (model == spv::ExecutionModel::TessellationControl) { + const Id gl_per_vertex_array{TypeArray(gl_per_vertex_type, Constant(uint_id, 4U))}; + gl_out = AddOutput(gl_per_vertex_array); + + const Id arr2_id{TypeArray(float_id, Constant(uint_id, 2U))}; + gl_tess_level_inner = AddOutput(arr2_id); + Decorate(gl_tess_level_inner, spv::Decoration::BuiltIn, spv::BuiltIn::TessLevelInner); + Decorate(gl_tess_level_inner, spv::Decoration::Patch); + + const Id arr4_id{TypeArray(float_id, Constant(uint_id, 4U))}; + gl_tess_level_outer = AddOutput(arr4_id); + Decorate(gl_tess_level_outer, spv::Decoration::BuiltIn, spv::BuiltIn::TessLevelOuter); + Decorate(gl_tess_level_outer, spv::Decoration::Patch); + } else { + gl_per_vertex = AddOutput(gl_per_vertex_type); + } + for (int i = 0; i < fs_info.num_inputs; i++) { + outputs[i] = AddOutput(model == spv::ExecutionModel::TessellationControl + ? TypeArray(vec4_id, Int(4)) + : vec4_id); + Decorate(outputs[i], spv::Decoration::Location, fs_info.inputs[i].param_index); + } + } + + void DefineInputs(spv::ExecutionModel model) { + if (model == spv::ExecutionModel::TessellationEvaluation) { + gl_tess_coord = AddInput(vec3_id); + Decorate(gl_tess_coord, spv::Decoration::BuiltIn, spv::BuiltIn::TessCoord); + } else { + gl_invocation_id = AddInput(int_id); + Decorate(gl_invocation_id, spv::Decoration::BuiltIn, spv::BuiltIn::InvocationId); + } + const Id gl_per_vertex_array{TypeArray(gl_per_vertex_type, Constant(uint_id, 32U))}; + gl_in = AddInput(gl_per_vertex_array); + const Id float_arr{TypeArray(vec4_id, Int(32))}; + for (int i = 0; i < fs_info.num_inputs; i++) { + inputs[i] = AddInput(float_arr); + Decorate(inputs[i], spv::Decoration::Location, fs_info.inputs[i].param_index); + } + } + +private: + FragmentRuntimeInfo fs_info; + Id main; + Id void_id; + Id bool_id; + Id float_id; + Id uint_id; + Id int_id; + Id bvec2_id; + Id vec2_id; + Id vec3_id; + Id vec4_id; + Id float_one; + Id float_min_one; + Id int_zero; + Id gl_per_vertex_type; + Id gl_in; + union { + Id gl_out; + Id gl_per_vertex; + }; + Id gl_tess_level_inner; + Id gl_tess_level_outer; + union { + Id gl_tess_coord; + Id gl_invocation_id; + }; + std::vector inputs; + std::vector outputs; + std::vector interfaces; +}; + +std::vector EmitAuxilaryTessShader(AuxShaderType type, const FragmentRuntimeInfo& fs_info) { + QuadRectListEmitter ctx{fs_info}; + switch (type) { + case AuxShaderType::RectListTCS: + ctx.EmitRectListTCS(); + break; + case AuxShaderType::QuadListTCS: + ctx.EmitQuadListTCS(); + break; + case AuxShaderType::PassthroughTES: + ctx.EmitPassthroughTES(); + break; + } + return ctx.Assemble(); +} + +} // namespace Shader::Backend::SPIRV \ No newline at end of file diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_quad_rect.h b/src/shader_recompiler/backend/spirv/emit_spirv_quad_rect.h new file mode 100644 index 000000000..c6c970ec3 --- /dev/null +++ b/src/shader_recompiler/backend/spirv/emit_spirv_quad_rect.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/types.h" + +namespace Shader { +struct FragmentRuntimeInfo; +} + +namespace Shader::Backend::SPIRV { + +enum class AuxShaderType : u32 { + RectListTCS, + QuadListTCS, + PassthroughTES, +}; + +[[nodiscard]] std::vector EmitAuxilaryTessShader(AuxShaderType type, + const FragmentRuntimeInfo& fs_info); + +} // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 255a3e2b2..d8bafccd9 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -772,7 +772,7 @@ Id ImageType(EmitContext& ctx, const ImageResource& desc, Id sampled_type) { const auto image = desc.GetSharp(ctx.info); const auto format = desc.is_atomic ? GetFormat(image) : spv::ImageFormat::Unknown; const auto type = image.GetBoundType(); - const u32 sampled = desc.is_storage ? 2 : 1; + const u32 sampled = desc.IsStorage(image) ? 2 : 1; switch (type) { case AmdGpu::ImageType::Color1D: return ctx.TypeImage(sampled_type, spv::Dim::Dim1D, false, false, false, sampled, format); @@ -800,6 +800,7 @@ void EmitContext::DefineImagesAndSamplers() { const auto sharp = image_desc.GetSharp(info); const auto nfmt = sharp.GetNumberFmt(); const bool is_integer = AmdGpu::IsInteger(nfmt); + const bool is_storage = image_desc.IsStorage(sharp); const VectorIds& data_types = GetAttributeType(*this, nfmt); const Id sampled_type = data_types[1]; const Id image_type{ImageType(*this, image_desc, sampled_type)}; @@ -811,11 +812,11 @@ void EmitContext::DefineImagesAndSamplers() { images.push_back({ .data_types = &data_types, .id = id, - .sampled_type = image_desc.is_storage ? sampled_type : TypeSampledImage(image_type), + .sampled_type = is_storage ? sampled_type : TypeSampledImage(image_type), .pointer_type = pointer_type, .image_type = image_type, .is_integer = is_integer, - .is_storage = image_desc.is_storage, + .is_storage = is_storage, }); interfaces.push_back(id); } diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index 116935b94..4408cae28 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later + #include "shader_recompiler/frontend/translate/translate.h" #include "shader_recompiler/ir/reg.h" +#include "shader_recompiler/profile.h" #include "shader_recompiler/runtime_info.h" namespace Shader::Gcn { @@ -203,6 +205,7 @@ void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool strid addr, ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0))); ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); } + emit_ds_read_barrier = true; } void Translator::DS_SWIZZLE_B32(const GcnInst& inst) { @@ -219,6 +222,11 @@ void Translator::DS_SWIZZLE_B32(const GcnInst& inst) { void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst) { + if (emit_ds_read_barrier && profile.needs_lds_barriers) { + ir.Barrier(); + emit_ds_read_barrier = false; + } + const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; IR::VectorReg dst_reg{inst.dst[0].code}; if (is_pair) { diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 218b66d74..fd4d8d86a 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -306,6 +306,7 @@ private: const RuntimeInfo& runtime_info; const Profile& profile; bool opcode_missing = false; + bool emit_ds_read_barrier = false; }; void Translate(IR::Block* block, u32 block_base, std::span inst_list, Info& info, diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 072b1f88e..48cb79610 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -420,13 +420,13 @@ void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) { IR::TextureInstInfo info{}; info.has_lod.Assign(has_mip); - const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info); + const IR::Value texel = ir.ImageRead(handle, body, {}, {}, info); for (u32 i = 0; i < 4; i++) { if (((mimg.dmask >> i) & 1) == 0) { continue; } - IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; + IR::U32 value = IR::U32{ir.CompositeExtract(texel, i)}; ir.SetVectorReg(dest_reg++, value); } } @@ -454,7 +454,7 @@ void Translator::IMAGE_STORE(bool has_mip, const GcnInst& inst) { comps.push_back(ir.GetVectorReg(data_reg++)); } const IR::Value value = ir.CompositeConstruct(comps[0], comps[1], comps[2], comps[3]); - ir.ImageWrite(handle, body, {}, value, info); + ir.ImageWrite(handle, body, {}, {}, value, info); } void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) { diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index dbea2af8a..b6ac12785 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -49,11 +49,11 @@ struct BufferResource { u8 instance_attrib{}; bool is_written{}; - bool IsStorage(AmdGpu::Buffer buffer) const noexcept { + [[nodiscard]] bool IsStorage(const AmdGpu::Buffer& buffer) const noexcept { return buffer.GetSize() > MaxUboSize || is_written || is_gds_buffer; } - constexpr AmdGpu::Buffer GetSharp(const Info& info) const noexcept; + [[nodiscard]] constexpr AmdGpu::Buffer GetSharp(const Info& info) const noexcept; }; using BufferResourceList = boost::container::small_vector; @@ -61,18 +61,24 @@ struct TextureBufferResource { u32 sharp_idx; bool is_written{}; - constexpr AmdGpu::Buffer GetSharp(const Info& info) const noexcept; + [[nodiscard]] constexpr AmdGpu::Buffer GetSharp(const Info& info) const noexcept; }; using TextureBufferResourceList = boost::container::small_vector; struct ImageResource { u32 sharp_idx; - bool is_storage{}; bool is_depth{}; bool is_atomic{}; bool is_array{}; + bool is_read{}; + bool is_written{}; - constexpr AmdGpu::Image GetSharp(const Info& info) const noexcept; + [[nodiscard]] bool IsStorage(const AmdGpu::Image& image) const noexcept { + // Need cube as storage when used with ImageRead. + return is_written || (is_read && image.GetBoundType() == AmdGpu::ImageType::Cube); + } + + [[nodiscard]] constexpr AmdGpu::Image GetSharp(const Info& info) const noexcept; }; using ImageResourceList = boost::container::small_vector; diff --git a/src/shader_recompiler/ir/breadth_first_search.h b/src/shader_recompiler/ir/breadth_first_search.h index b3ed87204..390dffb5c 100644 --- a/src/shader_recompiler/ir/breadth_first_search.h +++ b/src/shader_recompiler/ir/breadth_first_search.h @@ -1,74 +1,74 @@ -// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include "shader_recompiler/ir/value.h" - -namespace Shader::IR { - -// Use typename Instruction so the function can be used to return either const or mutable -// Insts depending on the context. -template -auto BreadthFirstSearch(Instruction* inst, - Pred&& pred) -> std::invoke_result_t { - // Most often case the instruction is the desired already. - if (std::optional result = pred(inst)) { - return result; - } - - // Breadth-first search visiting the right most arguments first - boost::container::small_vector visited; - std::queue queue; - queue.push(inst); - - while (!queue.empty()) { - // Pop one instruction from the queue - Instruction* inst{queue.front()}; - queue.pop(); - if (std::optional result = pred(inst)) { - // This is the instruction we were looking for - return result; - } - // Visit the right most arguments first - for (size_t arg = inst->NumArgs(); arg--;) { - Value arg_value{inst->Arg(arg)}; - if (arg_value.IsImmediate()) { - continue; - } - // Queue instruction if it hasn't been visited - Instruction* arg_inst{arg_value.InstRecursive()}; - if (std::ranges::find(visited, arg_inst) == visited.end()) { - visited.push_back(arg_inst); - queue.push(arg_inst); - } - } - } - // SSA tree has been traversed and the result hasn't been found - return std::nullopt; -} - -template -auto BreadthFirstSearch(const Value& value, - Pred&& pred) -> std::invoke_result_t { - if (value.IsImmediate()) { - // Nothing to do with immediates - return std::nullopt; - } - return BreadthFirstSearch(value.InstRecursive(), pred); -} - -template -auto BreadthFirstSearch(Value value, Pred&& pred) -> std::invoke_result_t { - if (value.IsImmediate()) { - // Nothing to do with immediates - return std::nullopt; - } - return BreadthFirstSearch(value.InstRecursive(), pred); -} - -} // namespace Shader::IR +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include "shader_recompiler/ir/value.h" + +namespace Shader::IR { + +// Use typename Instruction so the function can be used to return either const or mutable +// Insts depending on the context. +template +auto BreadthFirstSearch(Instruction* inst, + Pred&& pred) -> std::invoke_result_t { + // Most often case the instruction is the desired already. + if (std::optional result = pred(inst)) { + return result; + } + + // Breadth-first search visiting the right most arguments first + boost::container::small_vector visited; + std::queue queue; + queue.push(inst); + + while (!queue.empty()) { + // Pop one instruction from the queue + Instruction* inst{queue.front()}; + queue.pop(); + if (std::optional result = pred(inst)) { + // This is the instruction we were looking for + return result; + } + // Visit the right most arguments first + for (size_t arg = inst->NumArgs(); arg--;) { + Value arg_value{inst->Arg(arg)}; + if (arg_value.IsImmediate()) { + continue; + } + // Queue instruction if it hasn't been visited + Instruction* arg_inst{arg_value.InstRecursive()}; + if (std::ranges::find(visited, arg_inst) == visited.end()) { + visited.push_back(arg_inst); + queue.push(arg_inst); + } + } + } + // SSA tree has been traversed and the result hasn't been found + return std::nullopt; +} + +template +auto BreadthFirstSearch(const Value& value, + Pred&& pred) -> std::invoke_result_t { + if (value.IsImmediate()) { + // Nothing to do with immediates + return std::nullopt; + } + return BreadthFirstSearch(value.InstRecursive(), pred); +} + +template +auto BreadthFirstSearch(Value value, Pred&& pred) -> std::invoke_result_t { + if (value.IsImmediate()) { + // Nothing to do with immediates + return std::nullopt; + } + return BreadthFirstSearch(value.InstRecursive(), pred); +} + +} // namespace Shader::IR diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 21df53391..c241ec984 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -1630,11 +1630,6 @@ Value IREmitter::ImageGatherDref(const Value& handle, const Value& coords, const return Inst(Opcode::ImageGatherDref, Flags{info}, handle, coords, offset, dref); } -Value IREmitter::ImageFetch(const Value& handle, const Value& coords, const U32& lod, - const Value& offset, const U32& multisampling, TextureInstInfo info) { - return Inst(Opcode::ImageFetch, Flags{info}, handle, coords, lod, offset, multisampling); -} - Value IREmitter::ImageQueryDimension(const Value& handle, const IR::U32& lod, const IR::U1& skip_mips) { return Inst(Opcode::ImageQueryDimensions, handle, lod, skip_mips); @@ -1657,13 +1652,13 @@ Value IREmitter::ImageGradient(const Value& handle, const Value& coords, } Value IREmitter::ImageRead(const Value& handle, const Value& coords, const U32& lod, - TextureInstInfo info) { - return Inst(Opcode::ImageRead, Flags{info}, handle, coords, lod); + const U32& multisampling, TextureInstInfo info) { + return Inst(Opcode::ImageRead, Flags{info}, handle, coords, lod, multisampling); } void IREmitter::ImageWrite(const Value& handle, const Value& coords, const U32& lod, - const Value& color, TextureInstInfo info) { - Inst(Opcode::ImageWrite, Flags{info}, handle, coords, lod, color); + const U32& multisampling, const Value& color, TextureInstInfo info) { + Inst(Opcode::ImageWrite, Flags{info}, handle, coords, lod, multisampling, color); } // Debug print maps to SPIRV's NonSemantic DebugPrintf instruction diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 95713565b..4cf44107e 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -325,17 +325,14 @@ public: TextureInstInfo info); [[nodiscard]] Value ImageGatherDref(const Value& handle, const Value& coords, const Value& offset, const F32& dref, TextureInstInfo info); - [[nodiscard]] Value ImageFetch(const Value& handle, const Value& coords, const U32& lod, - const Value& offset, const U32& multisampling, - TextureInstInfo info); [[nodiscard]] Value ImageGradient(const Value& handle, const Value& coords, const Value& derivatives_dx, const Value& derivatives_dy, const Value& offset, const F32& lod_clamp, TextureInstInfo info); [[nodiscard]] Value ImageRead(const Value& handle, const Value& coords, const U32& lod, - TextureInstInfo info); - void ImageWrite(const Value& handle, const Value& coords, const U32& lod, const Value& color, - TextureInstInfo info); + const U32& multisampling, TextureInstInfo info); + void ImageWrite(const Value& handle, const Value& coords, const U32& lod, + const U32& multisampling, const Value& color, TextureInstInfo info); void EmitVertex(); void EmitPrimitive(); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 470f9fbe5..aafd43ea8 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -338,12 +338,11 @@ OPCODE(ImageSampleDrefImplicitLod, F32x4, Opaq OPCODE(ImageSampleDrefExplicitLod, F32x4, Opaque, Opaque, F32, F32, Opaque, ) OPCODE(ImageGather, F32x4, Opaque, Opaque, Opaque, ) OPCODE(ImageGatherDref, F32x4, Opaque, Opaque, Opaque, F32, ) -OPCODE(ImageFetch, F32x4, Opaque, Opaque, U32, Opaque, Opaque, ) OPCODE(ImageQueryDimensions, U32x4, Opaque, U32, U1, ) OPCODE(ImageQueryLod, F32x4, Opaque, Opaque, ) OPCODE(ImageGradient, F32x4, Opaque, Opaque, Opaque, Opaque, Opaque, F32, ) -OPCODE(ImageRead, U32x4, Opaque, Opaque, U32, ) -OPCODE(ImageWrite, Void, Opaque, Opaque, U32, U32x4, ) +OPCODE(ImageRead, U32x4, Opaque, Opaque, U32, U32, ) +OPCODE(ImageWrite, Void, Opaque, Opaque, U32, U32, U32x4, ) // Image atomic operations OPCODE(ImageAtomicIAdd32, U32, Opaque, Opaque, U32, ) diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index a59398952..db1a2edd2 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -115,25 +115,16 @@ bool IsImageAtomicInstruction(const IR::Inst& inst) { } } -bool IsImageStorageInstruction(const IR::Inst& inst) { - switch (inst.GetOpcode()) { - case IR::Opcode::ImageWrite: - case IR::Opcode::ImageRead: - return true; - default: - return IsImageAtomicInstruction(inst); - } -} - bool IsImageInstruction(const IR::Inst& inst) { switch (inst.GetOpcode()) { - case IR::Opcode::ImageFetch: + case IR::Opcode::ImageRead: + case IR::Opcode::ImageWrite: case IR::Opcode::ImageQueryDimensions: case IR::Opcode::ImageQueryLod: case IR::Opcode::ImageSampleRaw: return true; default: - return IsImageStorageInstruction(inst); + return IsImageAtomicInstruction(inst); } } @@ -201,7 +192,8 @@ public: return desc.sharp_idx == existing.sharp_idx; })}; auto& image = image_resources[index]; - image.is_storage |= desc.is_storage; + image.is_read |= desc.is_read; + image.is_written |= desc.is_written; return index; } @@ -429,9 +421,9 @@ void PatchTextureBufferInstruction(IR::Block& block, IR::Inst& inst, Info& info, } IR::Value PatchCubeCoord(IR::IREmitter& ir, const IR::Value& s, const IR::Value& t, - const IR::Value& z, bool is_storage, bool is_array) { + const IR::Value& z, bool is_written, bool is_array) { // When cubemap is written with imageStore it is treated like 2DArray. - if (is_storage) { + if (is_written) { return ir.CompositeConstruct(s, t, z); } @@ -684,15 +676,16 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip image = AmdGpu::Image::Null(); } ASSERT(image.GetType() != AmdGpu::ImageType::Invalid); - const bool is_storage = IsImageStorageInstruction(inst); + const bool is_read = inst.GetOpcode() == IR::Opcode::ImageRead; + const bool is_written = inst.GetOpcode() == IR::Opcode::ImageWrite; // Patch image instruction if image is FMask. if (image.IsFmask()) { - ASSERT_MSG(!is_storage, "FMask storage instructions are not supported"); + ASSERT_MSG(!is_written, "FMask storage instructions are not supported"); IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; switch (inst.GetOpcode()) { - case IR::Opcode::ImageFetch: + case IR::Opcode::ImageRead: case IR::Opcode::ImageSampleRaw: { IR::F32 fmaskx = ir.BitCast(ir.Imm32(0x76543210)); IR::F32 fmasky = ir.BitCast(ir.Imm32(0xfedcba98)); @@ -721,10 +714,11 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip u32 image_binding = descriptors.Add(ImageResource{ .sharp_idx = tsharp, - .is_storage = is_storage, .is_depth = bool(inst_info.is_depth), .is_atomic = IsImageAtomicInstruction(inst), .is_array = bool(inst_info.is_array), + .is_read = is_read, + .is_written = is_written, }); // Sample instructions must be resolved into a new instruction using address register data. @@ -762,7 +756,7 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip case AmdGpu::ImageType::Color3D: // x, y, z, [lod] return {ir.CompositeConstruct(body->Arg(0), body->Arg(1), body->Arg(2)), body->Arg(3)}; case AmdGpu::ImageType::Cube: // x, y, face, [lod] - return {PatchCubeCoord(ir, body->Arg(0), body->Arg(1), body->Arg(2), is_storage, + return {PatchCubeCoord(ir, body->Arg(0), body->Arg(1), body->Arg(2), is_written, inst_info.is_array), body->Arg(3)}; default: @@ -772,19 +766,20 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip inst.SetArg(1, coords); if (inst.GetOpcode() == IR::Opcode::ImageWrite) { - inst.SetArg(3, SwizzleVector(ir, image, inst.Arg(3))); + inst.SetArg(4, SwizzleVector(ir, image, inst.Arg(4))); } if (inst_info.has_lod) { - ASSERT(inst.GetOpcode() == IR::Opcode::ImageFetch || - inst.GetOpcode() == IR::Opcode::ImageRead || + ASSERT(inst.GetOpcode() == IR::Opcode::ImageRead || inst.GetOpcode() == IR::Opcode::ImageWrite); ASSERT(image.GetType() != AmdGpu::ImageType::Color2DMsaa && image.GetType() != AmdGpu::ImageType::Color2DMsaaArray); inst.SetArg(2, arg); - } else if (image.GetType() == AmdGpu::ImageType::Color2DMsaa || - image.GetType() == AmdGpu::ImageType::Color2DMsaaArray) { - inst.SetArg(4, arg); + } else if ((image.GetType() == AmdGpu::ImageType::Color2DMsaa || + image.GetType() == AmdGpu::ImageType::Color2DMsaaArray) && + (inst.GetOpcode() == IR::Opcode::ImageRead || + inst.GetOpcode() == IR::Opcode::ImageWrite)) { + inst.SetArg(3, arg); } } diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 23e23c118..bbf74f5d3 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -227,7 +227,7 @@ struct RuntimeInfo { ComputeRuntimeInfo cs_info; }; - RuntimeInfo(Stage stage_) { + void Initialize(Stage stage_) { memset(this, 0, sizeof(*this)); stage = stage_; } diff --git a/src/shader_recompiler/specialization.h b/src/shader_recompiler/specialization.h index 5799c4c95..5bf97ee51 100644 --- a/src/shader_recompiler/specialization.h +++ b/src/shader_recompiler/specialization.h @@ -39,10 +39,12 @@ struct TextureBufferSpecialization { struct ImageSpecialization { AmdGpu::ImageType type = AmdGpu::ImageType::Color2D; bool is_integer = false; + bool is_storage = false; u32 dst_select = 0; bool operator==(const ImageSpecialization& other) const { return type == other.type && is_integer == other.is_integer && + is_storage == other.is_storage && (dst_select != 0 ? dst_select == other.dst_select : true); } }; @@ -114,7 +116,8 @@ struct StageSpecialization { [](auto& spec, const auto& desc, AmdGpu::Image sharp) { spec.type = sharp.GetBoundType(); spec.is_integer = AmdGpu::IsInteger(sharp.GetNumberFmt()); - if (desc.is_storage) { + spec.is_storage = desc.IsStorage(sharp); + if (spec.is_storage) { spec.dst_select = sharp.DstSelect(); } }); diff --git a/src/video_core/amdgpu/resource.h b/src/video_core/amdgpu/resource.h index 5d7417559..d9a8b7cac 100644 --- a/src/video_core/amdgpu/resource.h +++ b/src/video_core/amdgpu/resource.h @@ -126,6 +126,7 @@ enum class TilingMode : u32 { Display_MacroTiled = 0xAu, Texture_MicroTiled = 0xDu, Texture_MacroTiled = 0xEu, + Texture_Volume = 0x13u, }; constexpr std::string_view NameOf(TilingMode type) { @@ -140,6 +141,8 @@ constexpr std::string_view NameOf(TilingMode type) { return "Texture_MicroTiled"; case TilingMode::Texture_MacroTiled: return "Texture_MacroTiled"; + case TilingMode::Texture_Volume: + return "Texture_Volume"; default: return "Unknown"; } @@ -294,9 +297,6 @@ struct Image { return tiling_index == 5 ? TilingMode::Texture_MicroTiled : TilingMode::Depth_MacroTiled; } - if (tiling_index == 0x13) { - return TilingMode::Texture_MicroTiled; - } return static_cast(tiling_index); } diff --git a/src/video_core/buffer_cache/buffer.h b/src/video_core/buffer_cache/buffer.h index f67278f64..feeafd9bd 100644 --- a/src/video_core/buffer_cache/buffer.h +++ b/src/video_core/buffer_cache/buffer.h @@ -1,203 +1,203 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include "common/types.h" -#include "video_core/amdgpu/resource.h" -#include "video_core/renderer_vulkan/vk_common.h" - -namespace Vulkan { -class Instance; -class Scheduler; -} // namespace Vulkan - -VK_DEFINE_HANDLE(VmaAllocation) -VK_DEFINE_HANDLE(VmaAllocator) - -struct VmaAllocationInfo; - -namespace VideoCore { - -/// Hints and requirements for the backing memory type of a commit -enum class MemoryUsage { - DeviceLocal, ///< Requests device local buffer. - Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads - Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks - Stream, ///< Requests device local host visible buffer, falling back host memory. -}; - -constexpr vk::BufferUsageFlags ReadFlags = - vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eUniformTexelBuffer | - vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eIndexBuffer | - vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eIndirectBuffer; - -constexpr vk::BufferUsageFlags AllFlags = ReadFlags | vk::BufferUsageFlagBits::eTransferDst | - vk::BufferUsageFlagBits::eStorageTexelBuffer | - vk::BufferUsageFlagBits::eStorageBuffer; - -struct UniqueBuffer { - explicit UniqueBuffer(vk::Device device, VmaAllocator allocator); - ~UniqueBuffer(); - - UniqueBuffer(const UniqueBuffer&) = delete; - UniqueBuffer& operator=(const UniqueBuffer&) = delete; - - UniqueBuffer(UniqueBuffer&& other) - : allocator{std::exchange(other.allocator, VK_NULL_HANDLE)}, - allocation{std::exchange(other.allocation, VK_NULL_HANDLE)}, - buffer{std::exchange(other.buffer, VK_NULL_HANDLE)} {} - UniqueBuffer& operator=(UniqueBuffer&& other) { - buffer = std::exchange(other.buffer, VK_NULL_HANDLE); - allocator = std::exchange(other.allocator, VK_NULL_HANDLE); - allocation = std::exchange(other.allocation, VK_NULL_HANDLE); - return *this; - } - - void Create(const vk::BufferCreateInfo& image_ci, MemoryUsage usage, - VmaAllocationInfo* out_alloc_info); - - operator vk::Buffer() const { - return buffer; - } - - vk::Device device; - VmaAllocator allocator; - VmaAllocation allocation; - vk::Buffer buffer{}; -}; - -class Buffer { -public: - explicit Buffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, - MemoryUsage usage, VAddr cpu_addr_, vk::BufferUsageFlags flags, - u64 size_bytes_); - - Buffer& operator=(const Buffer&) = delete; - Buffer(const Buffer&) = delete; - - Buffer& operator=(Buffer&&) = default; - Buffer(Buffer&&) = default; - - vk::BufferView View(u32 offset, u32 size, bool is_written, AmdGpu::DataFormat dfmt, - AmdGpu::NumberFormat nfmt); - - /// Increases the likeliness of this being a stream buffer - void IncreaseStreamScore(int score) noexcept { - stream_score += score; - } - - /// Returns the likeliness of this being a stream buffer - [[nodiscard]] int StreamScore() const noexcept { - return stream_score; - } - - /// Returns true when vaddr -> vaddr+size is fully contained in the buffer - [[nodiscard]] bool IsInBounds(VAddr addr, u64 size) const noexcept { - return addr >= cpu_addr && addr + size <= cpu_addr + SizeBytes(); - } - - /// Returns the base CPU address of the buffer - [[nodiscard]] VAddr CpuAddr() const noexcept { - return cpu_addr; - } - - /// Returns the offset relative to the given CPU address - [[nodiscard]] u32 Offset(VAddr other_cpu_addr) const noexcept { - return static_cast(other_cpu_addr - cpu_addr); - } - - size_t SizeBytes() const { - return size_bytes; - } - - vk::Buffer Handle() const noexcept { - return buffer; - } - - std::optional GetBarrier(vk::AccessFlagBits2 dst_acess_mask, - vk::PipelineStageFlagBits2 dst_stage) { - if (dst_acess_mask == access_mask && stage == dst_stage) { - return {}; - } - - auto barrier = vk::BufferMemoryBarrier2{ - .srcStageMask = stage, - .srcAccessMask = access_mask, - .dstStageMask = dst_stage, - .dstAccessMask = dst_acess_mask, - .buffer = buffer.buffer, - .size = size_bytes, - }; - access_mask = dst_acess_mask; - stage = dst_stage; - return barrier; - } - -public: - VAddr cpu_addr = 0; - bool is_picked{}; - bool is_coherent{}; - bool is_deleted{}; - int stream_score = 0; - size_t size_bytes = 0; - std::span mapped_data; - const Vulkan::Instance* instance; - Vulkan::Scheduler* scheduler; - MemoryUsage usage; - UniqueBuffer buffer; - vk::AccessFlagBits2 access_mask{vk::AccessFlagBits2::eNone}; - vk::PipelineStageFlagBits2 stage{vk::PipelineStageFlagBits2::eNone}; -}; - -class StreamBuffer : public Buffer { -public: - explicit StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, - MemoryUsage usage, u64 size_bytes_); - - /// Reserves a region of memory from the stream buffer. - std::pair Map(u64 size, u64 alignment = 0); - - /// Ensures that reserved bytes of memory are available to the GPU. - void Commit(); - - /// Maps and commits a memory region with user provided data - u64 Copy(VAddr src, size_t size, size_t alignment = 0) { - const auto [data, offset] = Map(size, alignment); - std::memcpy(data, reinterpret_cast(src), size); - Commit(); - return offset; - } - - u64 GetFreeSize() const { - return size_bytes - offset - mapped_size; - } - -private: - struct Watch { - u64 tick{}; - u64 upper_bound{}; - }; - - /// Increases the amount of watches available. - void ReserveWatches(std::vector& watches, std::size_t grow_size); - - /// Waits pending watches until requested upper bound. - void WaitPendingOperations(u64 requested_upper_bound); - -private: - u64 offset{}; - u64 mapped_size{}; - std::vector current_watches; - std::size_t current_watch_cursor{}; - std::optional invalidation_mark; - std::vector previous_watches; - std::size_t wait_cursor{}; - u64 wait_bound{}; -}; - -} // namespace VideoCore +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include "common/types.h" +#include "video_core/amdgpu/resource.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Vulkan { +class Instance; +class Scheduler; +} // namespace Vulkan + +VK_DEFINE_HANDLE(VmaAllocation) +VK_DEFINE_HANDLE(VmaAllocator) + +struct VmaAllocationInfo; + +namespace VideoCore { + +/// Hints and requirements for the backing memory type of a commit +enum class MemoryUsage { + DeviceLocal, ///< Requests device local buffer. + Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads + Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks + Stream, ///< Requests device local host visible buffer, falling back host memory. +}; + +constexpr vk::BufferUsageFlags ReadFlags = + vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eUniformTexelBuffer | + vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eIndexBuffer | + vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eIndirectBuffer; + +constexpr vk::BufferUsageFlags AllFlags = ReadFlags | vk::BufferUsageFlagBits::eTransferDst | + vk::BufferUsageFlagBits::eStorageTexelBuffer | + vk::BufferUsageFlagBits::eStorageBuffer; + +struct UniqueBuffer { + explicit UniqueBuffer(vk::Device device, VmaAllocator allocator); + ~UniqueBuffer(); + + UniqueBuffer(const UniqueBuffer&) = delete; + UniqueBuffer& operator=(const UniqueBuffer&) = delete; + + UniqueBuffer(UniqueBuffer&& other) + : allocator{std::exchange(other.allocator, VK_NULL_HANDLE)}, + allocation{std::exchange(other.allocation, VK_NULL_HANDLE)}, + buffer{std::exchange(other.buffer, VK_NULL_HANDLE)} {} + UniqueBuffer& operator=(UniqueBuffer&& other) { + buffer = std::exchange(other.buffer, VK_NULL_HANDLE); + allocator = std::exchange(other.allocator, VK_NULL_HANDLE); + allocation = std::exchange(other.allocation, VK_NULL_HANDLE); + return *this; + } + + void Create(const vk::BufferCreateInfo& image_ci, MemoryUsage usage, + VmaAllocationInfo* out_alloc_info); + + operator vk::Buffer() const { + return buffer; + } + + vk::Device device; + VmaAllocator allocator; + VmaAllocation allocation; + vk::Buffer buffer{}; +}; + +class Buffer { +public: + explicit Buffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, + MemoryUsage usage, VAddr cpu_addr_, vk::BufferUsageFlags flags, + u64 size_bytes_); + + Buffer& operator=(const Buffer&) = delete; + Buffer(const Buffer&) = delete; + + Buffer& operator=(Buffer&&) = default; + Buffer(Buffer&&) = default; + + vk::BufferView View(u32 offset, u32 size, bool is_written, AmdGpu::DataFormat dfmt, + AmdGpu::NumberFormat nfmt); + + /// Increases the likeliness of this being a stream buffer + void IncreaseStreamScore(int score) noexcept { + stream_score += score; + } + + /// Returns the likeliness of this being a stream buffer + [[nodiscard]] int StreamScore() const noexcept { + return stream_score; + } + + /// Returns true when vaddr -> vaddr+size is fully contained in the buffer + [[nodiscard]] bool IsInBounds(VAddr addr, u64 size) const noexcept { + return addr >= cpu_addr && addr + size <= cpu_addr + SizeBytes(); + } + + /// Returns the base CPU address of the buffer + [[nodiscard]] VAddr CpuAddr() const noexcept { + return cpu_addr; + } + + /// Returns the offset relative to the given CPU address + [[nodiscard]] u32 Offset(VAddr other_cpu_addr) const noexcept { + return static_cast(other_cpu_addr - cpu_addr); + } + + size_t SizeBytes() const { + return size_bytes; + } + + vk::Buffer Handle() const noexcept { + return buffer; + } + + std::optional GetBarrier(vk::AccessFlagBits2 dst_acess_mask, + vk::PipelineStageFlagBits2 dst_stage) { + if (dst_acess_mask == access_mask && stage == dst_stage) { + return {}; + } + + auto barrier = vk::BufferMemoryBarrier2{ + .srcStageMask = stage, + .srcAccessMask = access_mask, + .dstStageMask = dst_stage, + .dstAccessMask = dst_acess_mask, + .buffer = buffer.buffer, + .size = size_bytes, + }; + access_mask = dst_acess_mask; + stage = dst_stage; + return barrier; + } + +public: + VAddr cpu_addr = 0; + bool is_picked{}; + bool is_coherent{}; + bool is_deleted{}; + int stream_score = 0; + size_t size_bytes = 0; + std::span mapped_data; + const Vulkan::Instance* instance; + Vulkan::Scheduler* scheduler; + MemoryUsage usage; + UniqueBuffer buffer; + vk::AccessFlagBits2 access_mask{vk::AccessFlagBits2::eNone}; + vk::PipelineStageFlagBits2 stage{vk::PipelineStageFlagBits2::eNone}; +}; + +class StreamBuffer : public Buffer { +public: + explicit StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, + MemoryUsage usage, u64 size_bytes_); + + /// Reserves a region of memory from the stream buffer. + std::pair Map(u64 size, u64 alignment = 0); + + /// Ensures that reserved bytes of memory are available to the GPU. + void Commit(); + + /// Maps and commits a memory region with user provided data + u64 Copy(VAddr src, size_t size, size_t alignment = 0) { + const auto [data, offset] = Map(size, alignment); + std::memcpy(data, reinterpret_cast(src), size); + Commit(); + return offset; + } + + u64 GetFreeSize() const { + return size_bytes - offset - mapped_size; + } + +private: + struct Watch { + u64 tick{}; + u64 upper_bound{}; + }; + + /// Increases the amount of watches available. + void ReserveWatches(std::vector& watches, std::size_t grow_size); + + /// Waits pending watches until requested upper bound. + void WaitPendingOperations(u64 requested_upper_bound); + +private: + u64 offset{}; + u64 mapped_size{}; + std::vector current_watches; + std::size_t current_watch_cursor{}; + std::optional invalidation_mark; + std::vector previous_watches; + std::size_t wait_cursor{}; + u64 wait_bound{}; +}; + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index f265fb68d..59c1e0bc3 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -1,739 +1,696 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include "common/alignment.h" -#include "common/scope_exit.h" -#include "common/types.h" -#include "shader_recompiler/frontend/fetch_shader.h" -#include "shader_recompiler/info.h" -#include "video_core/amdgpu/liverpool.h" -#include "video_core/buffer_cache/buffer_cache.h" -#include "video_core/renderer_vulkan/liverpool_to_vk.h" -#include "video_core/renderer_vulkan/vk_instance.h" -#include "video_core/renderer_vulkan/vk_scheduler.h" -#include "video_core/texture_cache/texture_cache.h" - -namespace VideoCore { - -static constexpr size_t NumVertexBuffers = 32; -static constexpr size_t GdsBufferSize = 64_KB; -static constexpr size_t StagingBufferSize = 1_GB; -static constexpr size_t UboStreamBufferSize = 64_MB; - -BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, - AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_, - PageManager& tracker_) - : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, - texture_cache{texture_cache_}, tracker{tracker_}, - staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize}, - stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize}, - gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize}, - memory_tracker{&tracker} { - Vulkan::SetObjectName(instance.GetDevice(), gds_buffer.Handle(), "GDS Buffer"); - - // Ensure the first slot is used for the null buffer - const auto null_id = - slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, 0, ReadFlags, 1); - ASSERT(null_id.index == 0); - const vk::Buffer& null_buffer = slot_buffers[null_id].buffer; - Vulkan::SetObjectName(instance.GetDevice(), null_buffer, "Null Buffer"); - - const vk::BufferViewCreateInfo null_view_ci = { - .buffer = null_buffer, - .format = vk::Format::eR8Unorm, - .offset = 0, - .range = VK_WHOLE_SIZE, - }; - const auto [null_view_result, null_view] = instance.GetDevice().createBufferView(null_view_ci); - ASSERT_MSG(null_view_result == vk::Result::eSuccess, "Failed to create null buffer view."); - null_buffer_view = null_view; - Vulkan::SetObjectName(instance.GetDevice(), null_buffer_view, "Null Buffer View"); -} - -BufferCache::~BufferCache() = default; - -void BufferCache::InvalidateMemory(VAddr device_addr, u64 size) { - std::scoped_lock lk{mutex}; - const bool is_tracked = IsRegionRegistered(device_addr, size); - if (!is_tracked) { - return; - } - // Mark the page as CPU modified to stop tracking writes. - SCOPE_EXIT { - memory_tracker.MarkRegionAsCpuModified(device_addr, size); - }; - if (!memory_tracker.IsRegionGpuModified(device_addr, size)) { - // Page has not been modified by the GPU, nothing to do. - return; - } -} - -void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size) { - boost::container::small_vector copies; - u64 total_size_bytes = 0; - memory_tracker.ForEachDownloadRange( - device_addr, size, [&](u64 device_addr_out, u64 range_size) { - const VAddr buffer_addr = buffer.CpuAddr(); - const auto add_download = [&](VAddr start, VAddr end) { - const u64 new_offset = start - buffer_addr; - const u64 new_size = end - start; - copies.push_back(vk::BufferCopy{ - .srcOffset = new_offset, - .dstOffset = total_size_bytes, - .size = new_size, - }); - total_size_bytes += new_size; - }; - gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, add_download); - gpu_modified_ranges.Subtract(device_addr_out, range_size); - }); - if (total_size_bytes == 0) { - return; - } - const auto [staging, offset] = staging_buffer.Map(total_size_bytes); - for (auto& copy : copies) { - // Modify copies to have the staging offset in mind - copy.dstOffset += offset; - } - staging_buffer.Commit(); - scheduler.EndRendering(); - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.copyBuffer(buffer.buffer, staging_buffer.Handle(), copies); - scheduler.Finish(); - for (const auto& copy : copies) { - const VAddr copy_device_addr = buffer.CpuAddr() + copy.srcOffset; - const u64 dst_offset = copy.dstOffset - offset; - std::memcpy(std::bit_cast(copy_device_addr), staging + dst_offset, copy.size); - } -} - -bool BufferCache::BindVertexBuffers( - const Shader::Info& vs_info, const std::optional& fetch_shader) { - boost::container::small_vector attributes; - boost::container::small_vector bindings; - SCOPE_EXIT { - if (instance.IsVertexInputDynamicState()) { - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.setVertexInputEXT(bindings, attributes); - } else if (bindings.empty()) { - // Required to call bindVertexBuffers2EXT at least once in the current command buffer - // with non-null strides without a non-dynamic stride pipeline in between. Thus even - // when nothing is bound we still need to make a dummy call. Non-null strides in turn - // requires a count greater than 0. - const auto cmdbuf = scheduler.CommandBuffer(); - const std::array null_buffers = {GetBuffer(NULL_BUFFER_ID).buffer.buffer}; - constexpr std::array null_offsets = {static_cast(0)}; - cmdbuf.bindVertexBuffers2EXT(0, null_buffers, null_offsets, null_offsets, null_offsets); - } - }; - - if (!fetch_shader || fetch_shader->attributes.empty()) { - return false; - } - - std::array host_buffers; - std::array host_offsets; - std::array host_sizes; - std::array host_strides; - boost::container::static_vector guest_buffers; - - struct BufferRange { - VAddr base_address; - VAddr end_address; - vk::Buffer vk_buffer; - u64 offset; - - size_t GetSize() const { - return end_address - base_address; - } - }; - - // Calculate buffers memory overlaps - bool has_step_rate = false; - boost::container::static_vector ranges{}; - for (const auto& attrib : fetch_shader->attributes) { - if (attrib.UsesStepRates()) { - has_step_rate = true; - continue; - } - - const auto& buffer = attrib.GetSharp(vs_info); - if (buffer.GetSize() == 0) { - continue; - } - guest_buffers.emplace_back(buffer); - ranges.emplace_back(buffer.base_address, buffer.base_address + buffer.GetSize()); - attributes.push_back({ - .location = attrib.semantic, - .binding = attrib.semantic, - .format = - Vulkan::LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt()), - .offset = 0, - }); - bindings.push_back({ - .binding = attrib.semantic, - .stride = buffer.GetStride(), - .inputRate = attrib.GetStepRate() == Shader::Gcn::VertexAttribute::InstanceIdType::None - ? vk::VertexInputRate::eVertex - : vk::VertexInputRate::eInstance, - .divisor = 1, - }); - } - if (ranges.empty()) { - return false; - } - - std::ranges::sort(ranges, [](const BufferRange& lhv, const BufferRange& rhv) { - return lhv.base_address < rhv.base_address; - }); - - boost::container::static_vector ranges_merged{ranges[0]}; - for (auto range : ranges) { - auto& prev_range = ranges_merged.back(); - if (prev_range.end_address < range.base_address) { - ranges_merged.emplace_back(range); - } else { - prev_range.end_address = std::max(prev_range.end_address, range.end_address); - } - } - - // Map buffers - for (auto& range : ranges_merged) { - const auto [buffer, offset] = ObtainBuffer(range.base_address, range.GetSize(), false); - range.vk_buffer = buffer->buffer; - range.offset = offset; - } - - // Bind vertex buffers - const size_t num_buffers = guest_buffers.size(); - for (u32 i = 0; i < num_buffers; ++i) { - const auto& buffer = guest_buffers[i]; - const auto host_buffer = std::ranges::find_if(ranges_merged, [&](const BufferRange& range) { - return (buffer.base_address >= range.base_address && - buffer.base_address < range.end_address); - }); - ASSERT(host_buffer != ranges_merged.cend()); - - host_buffers[i] = host_buffer->vk_buffer; - host_offsets[i] = host_buffer->offset + buffer.base_address - host_buffer->base_address; - host_sizes[i] = buffer.GetSize(); - host_strides[i] = buffer.GetStride(); - } - - if (num_buffers > 0) { - const auto cmdbuf = scheduler.CommandBuffer(); - if (instance.IsVertexInputDynamicState()) { - cmdbuf.bindVertexBuffers(0, num_buffers, host_buffers.data(), host_offsets.data()); - } else { - cmdbuf.bindVertexBuffers2EXT(0, num_buffers, host_buffers.data(), host_offsets.data(), - host_sizes.data(), host_strides.data()); - } - } - - return has_step_rate; -} - -u32 BufferCache::BindIndexBuffer(bool& is_indexed, u32 index_offset) { - // Emulate QuadList and Polygon primitive types with CPU made index buffer. - const auto& regs = liverpool->regs; - if (!is_indexed) { - bool needs_index_buffer = false; - if (regs.primitive_type == AmdGpu::PrimitiveType::QuadList || - regs.primitive_type == AmdGpu::PrimitiveType::Polygon) { - needs_index_buffer = true; - } - - if (!needs_index_buffer) { - return regs.num_indices; - } - - // Emit indices. - const u32 index_size = 3 * regs.num_indices; - const auto [data, offset] = stream_buffer.Map(index_size); - - switch (regs.primitive_type) { - case AmdGpu::PrimitiveType::QuadList: - Vulkan::LiverpoolToVK::EmitQuadToTriangleListIndices(data, regs.num_indices); - break; - case AmdGpu::PrimitiveType::Polygon: - Vulkan::LiverpoolToVK::EmitPolygonToTriangleListIndices(data, regs.num_indices); - break; - default: - UNREACHABLE(); - break; - } - - stream_buffer.Commit(); - - // Bind index buffer. - is_indexed = true; - - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.bindIndexBuffer(stream_buffer.Handle(), offset, vk::IndexType::eUint16); - return index_size / sizeof(u16); - } - - // Figure out index type and size. - const bool is_index16 = - regs.index_buffer_type.index_type == AmdGpu::Liverpool::IndexType::Index16; - const vk::IndexType index_type = is_index16 ? vk::IndexType::eUint16 : vk::IndexType::eUint32; - const u32 index_size = is_index16 ? sizeof(u16) : sizeof(u32); - VAddr index_address = regs.index_base_address.Address(); - index_address += index_offset * index_size; - - if (regs.primitive_type == AmdGpu::PrimitiveType::QuadList) { - // Convert indices. - const u32 new_index_size = regs.num_indices * index_size * 6 / 4; - const auto [data, offset] = stream_buffer.Map(new_index_size); - const auto index_ptr = reinterpret_cast(index_address); - switch (index_type) { - case vk::IndexType::eUint16: - Vulkan::LiverpoolToVK::ConvertQuadToTriangleListIndices(data, index_ptr, - regs.num_indices); - break; - case vk::IndexType::eUint32: - Vulkan::LiverpoolToVK::ConvertQuadToTriangleListIndices(data, index_ptr, - regs.num_indices); - break; - default: - UNREACHABLE_MSG("Unsupported QuadList index type {}", vk::to_string(index_type)); - break; - } - stream_buffer.Commit(); - - // Bind index buffer. - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.bindIndexBuffer(stream_buffer.Handle(), offset, index_type); - return new_index_size / index_size; - } - if (regs.primitive_type == AmdGpu::PrimitiveType::Polygon) { - UNREACHABLE(); - } - - // Bind index buffer. - const u32 index_buffer_size = regs.num_indices * index_size; - const auto [vk_buffer, offset] = ObtainBuffer(index_address, index_buffer_size, false); - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.bindIndexBuffer(vk_buffer->Handle(), offset, index_type); - return regs.num_indices; -} - -void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) { - ASSERT_MSG(address % 4 == 0, "GDS offset must be dword aligned"); - if (!is_gds && !IsRegionRegistered(address, num_bytes)) { - memcpy(std::bit_cast(address), value, num_bytes); - return; - } - scheduler.EndRendering(); - const auto cmdbuf = scheduler.CommandBuffer(); - const Buffer* buffer = [&] { - if (is_gds) { - return &gds_buffer; - } - const BufferId buffer_id = FindBuffer(address, num_bytes); - return &slot_buffers[buffer_id]; - }(); - const vk::BufferMemoryBarrier2 buf_barrier = { - .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, - .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, - .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, - .dstAccessMask = vk::AccessFlagBits2::eMemoryRead, - .buffer = buffer->Handle(), - .offset = buffer->Offset(address), - .size = num_bytes, - }; - cmdbuf.pipelineBarrier2(vk::DependencyInfo{ - .dependencyFlags = vk::DependencyFlagBits::eByRegion, - .bufferMemoryBarrierCount = 1, - .pBufferMemoryBarriers = &buf_barrier, - }); - cmdbuf.updateBuffer(buffer->Handle(), buf_barrier.offset, num_bytes, value); -} - -std::pair BufferCache::ObtainHostUBO(std::span data) { - static constexpr u64 StreamThreshold = CACHING_PAGESIZE; - ASSERT(data.size_bytes() <= StreamThreshold); - const u64 offset = stream_buffer.Copy(reinterpret_cast(data.data()), data.size_bytes(), - instance.UniformMinAlignment()); - return {&stream_buffer, offset}; -} - -std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, bool is_written, - bool is_texel_buffer, BufferId buffer_id) { - // For small uniform buffers that have not been modified by gpu - // use device local stream buffer to reduce renderpass breaks. - static constexpr u64 StreamThreshold = CACHING_PAGESIZE; - const bool is_gpu_dirty = memory_tracker.IsRegionGpuModified(device_addr, size); - if (!is_written && size <= StreamThreshold && !is_gpu_dirty) { - const u64 offset = stream_buffer.Copy(device_addr, size, instance.UniformMinAlignment()); - return {&stream_buffer, offset}; - } - - if (!buffer_id || slot_buffers[buffer_id].is_deleted) { - buffer_id = FindBuffer(device_addr, size); - } - Buffer& buffer = slot_buffers[buffer_id]; - SynchronizeBuffer(buffer, device_addr, size, is_texel_buffer); - if (is_written) { - memory_tracker.MarkRegionAsGpuModified(device_addr, size); - gpu_modified_ranges.Add(device_addr, size); - } - return {&buffer, buffer.Offset(device_addr)}; -} - -std::pair BufferCache::ObtainViewBuffer(VAddr gpu_addr, u32 size, bool prefer_gpu) { - // Check if any buffer contains the full requested range. - const u64 page = gpu_addr >> CACHING_PAGEBITS; - const BufferId buffer_id = page_table[page]; - if (buffer_id) { - Buffer& buffer = slot_buffers[buffer_id]; - if (buffer.IsInBounds(gpu_addr, size)) { - SynchronizeBuffer(buffer, gpu_addr, size, false); - return {&buffer, buffer.Offset(gpu_addr)}; - } - } - // If no buffer contains the full requested range but some buffer within was GPU-modified, - // fall back to ObtainBuffer to create a full buffer and avoid losing GPU modifications. - // This is only done if the request prefers to use GPU memory, otherwise we can skip it. - if (prefer_gpu && memory_tracker.IsRegionGpuModified(gpu_addr, size)) { - return ObtainBuffer(gpu_addr, size, false, false); - } - // In all other cases, just do a CPU copy to the staging buffer. - const u32 offset = staging_buffer.Copy(gpu_addr, size, 16); - return {&staging_buffer, offset}; -} - -bool BufferCache::IsRegionRegistered(VAddr addr, size_t size) { - const VAddr end_addr = addr + size; - const u64 page_end = Common::DivCeil(end_addr, CACHING_PAGESIZE); - for (u64 page = addr >> CACHING_PAGEBITS; page < page_end;) { - const BufferId buffer_id = page_table[page]; - if (!buffer_id) { - ++page; - continue; - } - Buffer& buffer = slot_buffers[buffer_id]; - const VAddr buf_start_addr = buffer.CpuAddr(); - const VAddr buf_end_addr = buf_start_addr + buffer.SizeBytes(); - if (buf_start_addr < end_addr && addr < buf_end_addr) { - return true; - } - page = Common::DivCeil(buf_end_addr, CACHING_PAGESIZE); - } - return false; -} - -bool BufferCache::IsRegionCpuModified(VAddr addr, size_t size) { - return memory_tracker.IsRegionCpuModified(addr, size); -} - -bool BufferCache::IsRegionGpuModified(VAddr addr, size_t size) { - return memory_tracker.IsRegionGpuModified(addr, size); -} - -BufferId BufferCache::FindBuffer(VAddr device_addr, u32 size) { - if (device_addr == 0) { - return NULL_BUFFER_ID; - } - const u64 page = device_addr >> CACHING_PAGEBITS; - const BufferId buffer_id = page_table[page]; - if (!buffer_id) { - return CreateBuffer(device_addr, size); - } - const Buffer& buffer = slot_buffers[buffer_id]; - if (buffer.IsInBounds(device_addr, size)) { - return buffer_id; - } - return CreateBuffer(device_addr, size); -} - -BufferCache::OverlapResult BufferCache::ResolveOverlaps(VAddr device_addr, u32 wanted_size) { - static constexpr int STREAM_LEAP_THRESHOLD = 16; - boost::container::small_vector overlap_ids; - VAddr begin = device_addr; - VAddr end = device_addr + wanted_size; - int stream_score = 0; - bool has_stream_leap = false; - const auto expand_begin = [&](VAddr add_value) { - static constexpr VAddr min_page = CACHING_PAGESIZE + DEVICE_PAGESIZE; - if (add_value > begin - min_page) { - begin = min_page; - device_addr = DEVICE_PAGESIZE; - return; - } - begin -= add_value; - device_addr = begin - CACHING_PAGESIZE; - }; - const auto expand_end = [&](VAddr add_value) { - static constexpr VAddr max_page = 1ULL << MemoryTracker::MAX_CPU_PAGE_BITS; - if (add_value > max_page - end) { - end = max_page; - return; - } - end += add_value; - }; - if (begin == 0) { - return OverlapResult{ - .ids = std::move(overlap_ids), - .begin = begin, - .end = end, - .has_stream_leap = has_stream_leap, - }; - } - for (; device_addr >> CACHING_PAGEBITS < Common::DivCeil(end, CACHING_PAGESIZE); - device_addr += CACHING_PAGESIZE) { - const BufferId overlap_id = page_table[device_addr >> CACHING_PAGEBITS]; - if (!overlap_id) { - continue; - } - Buffer& overlap = slot_buffers[overlap_id]; - if (overlap.is_picked) { - continue; - } - overlap_ids.push_back(overlap_id); - overlap.is_picked = true; - const VAddr overlap_device_addr = overlap.CpuAddr(); - const bool expands_left = overlap_device_addr < begin; - if (expands_left) { - begin = overlap_device_addr; - } - const VAddr overlap_end = overlap_device_addr + overlap.SizeBytes(); - const bool expands_right = overlap_end > end; - if (overlap_end > end) { - end = overlap_end; - } - stream_score += overlap.StreamScore(); - if (stream_score > STREAM_LEAP_THRESHOLD && !has_stream_leap) { - // When this memory region has been joined a bunch of times, we assume it's being used - // as a stream buffer. Increase the size to skip constantly recreating buffers. - has_stream_leap = true; - if (expands_right) { - expand_begin(CACHING_PAGESIZE * 128); - } - if (expands_left) { - expand_end(CACHING_PAGESIZE * 128); - } - } - } - return OverlapResult{ - .ids = std::move(overlap_ids), - .begin = begin, - .end = end, - .has_stream_leap = has_stream_leap, - }; -} - -void BufferCache::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, - bool accumulate_stream_score) { - Buffer& new_buffer = slot_buffers[new_buffer_id]; - Buffer& overlap = slot_buffers[overlap_id]; - if (accumulate_stream_score) { - new_buffer.IncreaseStreamScore(overlap.StreamScore() + 1); - } - const size_t dst_base_offset = overlap.CpuAddr() - new_buffer.CpuAddr(); - const vk::BufferCopy copy = { - .srcOffset = 0, - .dstOffset = dst_base_offset, - .size = overlap.SizeBytes(), - }; - scheduler.EndRendering(); - const auto cmdbuf = scheduler.CommandBuffer(); - static constexpr vk::MemoryBarrier READ_BARRIER{ - .srcAccessMask = vk::AccessFlagBits::eMemoryWrite, - .dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite, - }; - static constexpr vk::MemoryBarrier WRITE_BARRIER{ - .srcAccessMask = vk::AccessFlagBits::eTransferWrite, - .dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, - }; - cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, - vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, - READ_BARRIER, {}, {}); - cmdbuf.copyBuffer(overlap.buffer, new_buffer.buffer, copy); - cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, - vk::PipelineStageFlagBits::eAllCommands, - vk::DependencyFlagBits::eByRegion, WRITE_BARRIER, {}, {}); - DeleteBuffer(overlap_id); -} - -BufferId BufferCache::CreateBuffer(VAddr device_addr, u32 wanted_size) { - const VAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE); - device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE); - wanted_size = static_cast(device_addr_end - device_addr); - const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size); - const u32 size = static_cast(overlap.end - overlap.begin); - const BufferId new_buffer_id = slot_buffers.insert( - instance, scheduler, MemoryUsage::DeviceLocal, overlap.begin, AllFlags, size); - auto& new_buffer = slot_buffers[new_buffer_id]; - const size_t size_bytes = new_buffer.SizeBytes(); - const auto cmdbuf = scheduler.CommandBuffer(); - scheduler.EndRendering(); - cmdbuf.fillBuffer(new_buffer.buffer, 0, size_bytes, 0); - for (const BufferId overlap_id : overlap.ids) { - JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap); - } - Register(new_buffer_id); - return new_buffer_id; -} - -void BufferCache::Register(BufferId buffer_id) { - ChangeRegister(buffer_id); -} - -void BufferCache::Unregister(BufferId buffer_id) { - ChangeRegister(buffer_id); -} - -template -void BufferCache::ChangeRegister(BufferId buffer_id) { - Buffer& buffer = slot_buffers[buffer_id]; - const auto size = buffer.SizeBytes(); - const VAddr device_addr_begin = buffer.CpuAddr(); - const VAddr device_addr_end = device_addr_begin + size; - const u64 page_begin = device_addr_begin / CACHING_PAGESIZE; - const u64 page_end = Common::DivCeil(device_addr_end, CACHING_PAGESIZE); - for (u64 page = page_begin; page != page_end; ++page) { - if constexpr (insert) { - page_table[page] = buffer_id; - } else { - page_table[page] = BufferId{}; - } - } -} - -void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, - bool is_texel_buffer) { - std::scoped_lock lk{mutex}; - boost::container::small_vector copies; - u64 total_size_bytes = 0; - u64 largest_copy = 0; - VAddr buffer_start = buffer.CpuAddr(); - memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) { - copies.push_back(vk::BufferCopy{ - .srcOffset = total_size_bytes, - .dstOffset = device_addr_out - buffer_start, - .size = range_size, - }); - total_size_bytes += range_size; - largest_copy = std::max(largest_copy, range_size); - }); - SCOPE_EXIT { - if (is_texel_buffer) { - SynchronizeBufferFromImage(buffer, device_addr, size); - } - }; - if (total_size_bytes == 0) { - return; - } - vk::Buffer src_buffer = staging_buffer.Handle(); - if (total_size_bytes < StagingBufferSize) { - const auto [staging, offset] = staging_buffer.Map(total_size_bytes); - for (auto& copy : copies) { - u8* const src_pointer = staging + copy.srcOffset; - const VAddr device_addr = buffer.CpuAddr() + copy.dstOffset; - std::memcpy(src_pointer, std::bit_cast(device_addr), copy.size); - // Apply the staging offset - copy.srcOffset += offset; - } - staging_buffer.Commit(); - } else { - // For large one time transfers use a temporary host buffer. - // RenderDoc can lag quite a bit if the stream buffer is too large. - Buffer temp_buffer{instance, - scheduler, - MemoryUsage::Upload, - 0, - vk::BufferUsageFlagBits::eTransferSrc, - total_size_bytes}; - src_buffer = temp_buffer.Handle(); - u8* const staging = temp_buffer.mapped_data.data(); - for (auto& copy : copies) { - u8* const src_pointer = staging + copy.srcOffset; - const VAddr device_addr = buffer.CpuAddr() + copy.dstOffset; - std::memcpy(src_pointer, std::bit_cast(device_addr), copy.size); - } - scheduler.DeferOperation([buffer = std::move(temp_buffer)]() mutable {}); - } - scheduler.EndRendering(); - const auto cmdbuf = scheduler.CommandBuffer(); - static constexpr vk::MemoryBarrier READ_BARRIER{ - .srcAccessMask = vk::AccessFlagBits::eMemoryWrite, - .dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite, - }; - static constexpr vk::MemoryBarrier WRITE_BARRIER{ - .srcAccessMask = vk::AccessFlagBits::eTransferWrite, - .dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, - }; - cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, - vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, - READ_BARRIER, {}, {}); - cmdbuf.copyBuffer(src_buffer, buffer.buffer, copies); - cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, - vk::PipelineStageFlagBits::eAllCommands, - vk::DependencyFlagBits::eByRegion, WRITE_BARRIER, {}, {}); -} - -bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size) { - static constexpr FindFlags find_flags = - FindFlags::NoCreate | FindFlags::RelaxDim | FindFlags::RelaxFmt | FindFlags::RelaxSize; - TextureCache::BaseDesc desc{}; - desc.info.guest_address = device_addr; - desc.info.guest_size_bytes = size; - const ImageId image_id = texture_cache.FindImage(desc, find_flags); - if (!image_id) { - return false; - } - Image& image = texture_cache.GetImage(image_id); - if (False(image.flags & ImageFlagBits::GpuModified)) { - return false; - } - ASSERT_MSG(device_addr == image.info.guest_address, - "Texel buffer aliases image subresources {:x} : {:x}", device_addr, - image.info.guest_address); - boost::container::small_vector copies; - u32 offset = buffer.Offset(image.info.guest_address); - const u32 num_layers = image.info.resources.layers; - const u32 max_offset = offset + size; - for (u32 m = 0; m < image.info.resources.levels; m++) { - const u32 width = std::max(image.info.size.width >> m, 1u); - const u32 height = std::max(image.info.size.height >> m, 1u); - const u32 depth = - image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; - const auto& [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; - offset += mip_ofs * num_layers; - if (offset + (mip_size * num_layers) > max_offset) { - break; - } - copies.push_back({ - .bufferOffset = offset, - .bufferRowLength = static_cast(mip_pitch), - .bufferImageHeight = static_cast(mip_height), - .imageSubresource{ - .aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, - .mipLevel = m, - .baseArrayLayer = 0, - .layerCount = num_layers, - }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, depth}, - }); - } - if (!copies.empty()) { - scheduler.EndRendering(); - image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.copyImageToBuffer(image.image, vk::ImageLayout::eTransferSrcOptimal, buffer.buffer, - copies); - } - return true; -} - -void BufferCache::DeleteBuffer(BufferId buffer_id) { - Buffer& buffer = slot_buffers[buffer_id]; - Unregister(buffer_id); - scheduler.DeferOperation([this, buffer_id] { slot_buffers.erase(buffer_id); }); - buffer.is_deleted = true; -} - -} // namespace VideoCore +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/alignment.h" +#include "common/scope_exit.h" +#include "common/types.h" +#include "shader_recompiler/frontend/fetch_shader.h" +#include "shader_recompiler/info.h" +#include "video_core/amdgpu/liverpool.h" +#include "video_core/buffer_cache/buffer_cache.h" +#include "video_core/renderer_vulkan/liverpool_to_vk.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/texture_cache/texture_cache.h" + +namespace VideoCore { + +static constexpr size_t NumVertexBuffers = 32; +static constexpr size_t GdsBufferSize = 64_KB; +static constexpr size_t StagingBufferSize = 1_GB; +static constexpr size_t UboStreamBufferSize = 64_MB; + +BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, + AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_, + PageManager& tracker_) + : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, + texture_cache{texture_cache_}, tracker{tracker_}, + staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize}, + stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize}, + gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize}, + memory_tracker{&tracker} { + Vulkan::SetObjectName(instance.GetDevice(), gds_buffer.Handle(), "GDS Buffer"); + + // Ensure the first slot is used for the null buffer + const auto null_id = + slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, 0, ReadFlags, 1); + ASSERT(null_id.index == 0); + const vk::Buffer& null_buffer = slot_buffers[null_id].buffer; + Vulkan::SetObjectName(instance.GetDevice(), null_buffer, "Null Buffer"); + + const vk::BufferViewCreateInfo null_view_ci = { + .buffer = null_buffer, + .format = vk::Format::eR8Unorm, + .offset = 0, + .range = VK_WHOLE_SIZE, + }; + const auto [null_view_result, null_view] = instance.GetDevice().createBufferView(null_view_ci); + ASSERT_MSG(null_view_result == vk::Result::eSuccess, "Failed to create null buffer view."); + null_buffer_view = null_view; + Vulkan::SetObjectName(instance.GetDevice(), null_buffer_view, "Null Buffer View"); +} + +BufferCache::~BufferCache() = default; + +void BufferCache::InvalidateMemory(VAddr device_addr, u64 size) { + std::scoped_lock lk{mutex}; + const bool is_tracked = IsRegionRegistered(device_addr, size); + if (!is_tracked) { + return; + } + // Mark the page as CPU modified to stop tracking writes. + SCOPE_EXIT { + memory_tracker.MarkRegionAsCpuModified(device_addr, size); + }; + if (!memory_tracker.IsRegionGpuModified(device_addr, size)) { + // Page has not been modified by the GPU, nothing to do. + return; + } +} + +void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size) { + boost::container::small_vector copies; + u64 total_size_bytes = 0; + memory_tracker.ForEachDownloadRange( + device_addr, size, [&](u64 device_addr_out, u64 range_size) { + const VAddr buffer_addr = buffer.CpuAddr(); + const auto add_download = [&](VAddr start, VAddr end) { + const u64 new_offset = start - buffer_addr; + const u64 new_size = end - start; + copies.push_back(vk::BufferCopy{ + .srcOffset = new_offset, + .dstOffset = total_size_bytes, + .size = new_size, + }); + total_size_bytes += new_size; + }; + gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, add_download); + gpu_modified_ranges.Subtract(device_addr_out, range_size); + }); + if (total_size_bytes == 0) { + return; + } + const auto [staging, offset] = staging_buffer.Map(total_size_bytes); + for (auto& copy : copies) { + // Modify copies to have the staging offset in mind + copy.dstOffset += offset; + } + staging_buffer.Commit(); + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.copyBuffer(buffer.buffer, staging_buffer.Handle(), copies); + scheduler.Finish(); + for (const auto& copy : copies) { + const VAddr copy_device_addr = buffer.CpuAddr() + copy.srcOffset; + const u64 dst_offset = copy.dstOffset - offset; + std::memcpy(std::bit_cast(copy_device_addr), staging + dst_offset, copy.size); + } +} + +bool BufferCache::BindVertexBuffers( + const Shader::Info& vs_info, const std::optional& fetch_shader) { + boost::container::small_vector attributes; + boost::container::small_vector bindings; + SCOPE_EXIT { + if (instance.IsVertexInputDynamicState()) { + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.setVertexInputEXT(bindings, attributes); + } else if (bindings.empty()) { + // Required to call bindVertexBuffers2EXT at least once in the current command buffer + // with non-null strides without a non-dynamic stride pipeline in between. Thus even + // when nothing is bound we still need to make a dummy call. Non-null strides in turn + // requires a count greater than 0. + const auto cmdbuf = scheduler.CommandBuffer(); + const std::array null_buffers = {GetBuffer(NULL_BUFFER_ID).buffer.buffer}; + constexpr std::array null_offsets = {static_cast(0)}; + cmdbuf.bindVertexBuffers2EXT(0, null_buffers, null_offsets, null_offsets, null_offsets); + } + }; + + if (!fetch_shader || fetch_shader->attributes.empty()) { + return false; + } + + std::array host_buffers; + std::array host_offsets; + std::array host_sizes; + std::array host_strides; + boost::container::static_vector guest_buffers; + + struct BufferRange { + VAddr base_address; + VAddr end_address; + vk::Buffer vk_buffer; + u64 offset; + + size_t GetSize() const { + return end_address - base_address; + } + }; + + // Calculate buffers memory overlaps + bool has_step_rate = false; + boost::container::static_vector ranges{}; + for (const auto& attrib : fetch_shader->attributes) { + if (attrib.UsesStepRates()) { + has_step_rate = true; + continue; + } + + const auto& buffer = attrib.GetSharp(vs_info); + if (buffer.GetSize() == 0) { + continue; + } + guest_buffers.emplace_back(buffer); + ranges.emplace_back(buffer.base_address, buffer.base_address + buffer.GetSize()); + attributes.push_back({ + .location = attrib.semantic, + .binding = attrib.semantic, + .format = + Vulkan::LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt()), + .offset = 0, + }); + bindings.push_back({ + .binding = attrib.semantic, + .stride = buffer.GetStride(), + .inputRate = attrib.GetStepRate() == Shader::Gcn::VertexAttribute::InstanceIdType::None + ? vk::VertexInputRate::eVertex + : vk::VertexInputRate::eInstance, + .divisor = 1, + }); + } + if (ranges.empty()) { + return false; + } + + std::ranges::sort(ranges, [](const BufferRange& lhv, const BufferRange& rhv) { + return lhv.base_address < rhv.base_address; + }); + + boost::container::static_vector ranges_merged{ranges[0]}; + for (auto range : ranges) { + auto& prev_range = ranges_merged.back(); + if (prev_range.end_address < range.base_address) { + ranges_merged.emplace_back(range); + } else { + prev_range.end_address = std::max(prev_range.end_address, range.end_address); + } + } + + // Map buffers + for (auto& range : ranges_merged) { + const auto [buffer, offset] = ObtainBuffer(range.base_address, range.GetSize(), false); + range.vk_buffer = buffer->buffer; + range.offset = offset; + } + + // Bind vertex buffers + const size_t num_buffers = guest_buffers.size(); + for (u32 i = 0; i < num_buffers; ++i) { + const auto& buffer = guest_buffers[i]; + const auto host_buffer = std::ranges::find_if(ranges_merged, [&](const BufferRange& range) { + return (buffer.base_address >= range.base_address && + buffer.base_address < range.end_address); + }); + ASSERT(host_buffer != ranges_merged.cend()); + + host_buffers[i] = host_buffer->vk_buffer; + host_offsets[i] = host_buffer->offset + buffer.base_address - host_buffer->base_address; + host_sizes[i] = buffer.GetSize(); + host_strides[i] = buffer.GetStride(); + } + + if (num_buffers > 0) { + const auto cmdbuf = scheduler.CommandBuffer(); + if (instance.IsVertexInputDynamicState()) { + cmdbuf.bindVertexBuffers(0, num_buffers, host_buffers.data(), host_offsets.data()); + } else { + cmdbuf.bindVertexBuffers2EXT(0, num_buffers, host_buffers.data(), host_offsets.data(), + host_sizes.data(), host_strides.data()); + } + } + + return has_step_rate; +} + +u32 BufferCache::BindIndexBuffer(bool& is_indexed, u32 index_offset) { + // Emulate QuadList and Polygon primitive types with CPU made index buffer. + const auto& regs = liverpool->regs; + if (!is_indexed) { + if (regs.primitive_type != AmdGpu::PrimitiveType::Polygon) { + return regs.num_indices; + } + + // Emit indices. + const u32 index_size = 3 * regs.num_indices; + const auto [data, offset] = stream_buffer.Map(index_size); + Vulkan::LiverpoolToVK::EmitPolygonToTriangleListIndices(data, regs.num_indices); + stream_buffer.Commit(); + + // Bind index buffer. + is_indexed = true; + + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.bindIndexBuffer(stream_buffer.Handle(), offset, vk::IndexType::eUint16); + return index_size / sizeof(u16); + } + + // Figure out index type and size. + const bool is_index16 = + regs.index_buffer_type.index_type == AmdGpu::Liverpool::IndexType::Index16; + const vk::IndexType index_type = is_index16 ? vk::IndexType::eUint16 : vk::IndexType::eUint32; + const u32 index_size = is_index16 ? sizeof(u16) : sizeof(u32); + VAddr index_address = regs.index_base_address.Address(); + index_address += index_offset * index_size; + + if (regs.primitive_type == AmdGpu::PrimitiveType::Polygon) { + UNREACHABLE(); + } + + // Bind index buffer. + const u32 index_buffer_size = regs.num_indices * index_size; + const auto [vk_buffer, offset] = ObtainBuffer(index_address, index_buffer_size, false); + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.bindIndexBuffer(vk_buffer->Handle(), offset, index_type); + return regs.num_indices; +} + +void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) { + ASSERT_MSG(address % 4 == 0, "GDS offset must be dword aligned"); + if (!is_gds && !IsRegionRegistered(address, num_bytes)) { + memcpy(std::bit_cast(address), value, num_bytes); + return; + } + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + const Buffer* buffer = [&] { + if (is_gds) { + return &gds_buffer; + } + const BufferId buffer_id = FindBuffer(address, num_bytes); + return &slot_buffers[buffer_id]; + }(); + const vk::BufferMemoryBarrier2 buf_barrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, + .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstAccessMask = vk::AccessFlagBits2::eMemoryRead, + .buffer = buffer->Handle(), + .offset = buffer->Offset(address), + .size = num_bytes, + }; + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &buf_barrier, + }); + cmdbuf.updateBuffer(buffer->Handle(), buf_barrier.offset, num_bytes, value); +} + +std::pair BufferCache::ObtainHostUBO(std::span data) { + static constexpr u64 StreamThreshold = CACHING_PAGESIZE; + ASSERT(data.size_bytes() <= StreamThreshold); + const u64 offset = stream_buffer.Copy(reinterpret_cast(data.data()), data.size_bytes(), + instance.UniformMinAlignment()); + return {&stream_buffer, offset}; +} + +std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, bool is_written, + bool is_texel_buffer, BufferId buffer_id) { + // For small uniform buffers that have not been modified by gpu + // use device local stream buffer to reduce renderpass breaks. + static constexpr u64 StreamThreshold = CACHING_PAGESIZE; + const bool is_gpu_dirty = memory_tracker.IsRegionGpuModified(device_addr, size); + if (!is_written && size <= StreamThreshold && !is_gpu_dirty) { + const u64 offset = stream_buffer.Copy(device_addr, size, instance.UniformMinAlignment()); + return {&stream_buffer, offset}; + } + + if (!buffer_id || slot_buffers[buffer_id].is_deleted) { + buffer_id = FindBuffer(device_addr, size); + } + Buffer& buffer = slot_buffers[buffer_id]; + SynchronizeBuffer(buffer, device_addr, size, is_texel_buffer); + if (is_written) { + memory_tracker.MarkRegionAsGpuModified(device_addr, size); + gpu_modified_ranges.Add(device_addr, size); + } + return {&buffer, buffer.Offset(device_addr)}; +} + +std::pair BufferCache::ObtainViewBuffer(VAddr gpu_addr, u32 size, bool prefer_gpu) { + // Check if any buffer contains the full requested range. + const u64 page = gpu_addr >> CACHING_PAGEBITS; + const BufferId buffer_id = page_table[page]; + if (buffer_id) { + Buffer& buffer = slot_buffers[buffer_id]; + if (buffer.IsInBounds(gpu_addr, size)) { + SynchronizeBuffer(buffer, gpu_addr, size, false); + return {&buffer, buffer.Offset(gpu_addr)}; + } + } + // If no buffer contains the full requested range but some buffer within was GPU-modified, + // fall back to ObtainBuffer to create a full buffer and avoid losing GPU modifications. + // This is only done if the request prefers to use GPU memory, otherwise we can skip it. + if (prefer_gpu && memory_tracker.IsRegionGpuModified(gpu_addr, size)) { + return ObtainBuffer(gpu_addr, size, false, false); + } + // In all other cases, just do a CPU copy to the staging buffer. + const u32 offset = staging_buffer.Copy(gpu_addr, size, 16); + return {&staging_buffer, offset}; +} + +bool BufferCache::IsRegionRegistered(VAddr addr, size_t size) { + const VAddr end_addr = addr + size; + const u64 page_end = Common::DivCeil(end_addr, CACHING_PAGESIZE); + for (u64 page = addr >> CACHING_PAGEBITS; page < page_end;) { + const BufferId buffer_id = page_table[page]; + if (!buffer_id) { + ++page; + continue; + } + Buffer& buffer = slot_buffers[buffer_id]; + const VAddr buf_start_addr = buffer.CpuAddr(); + const VAddr buf_end_addr = buf_start_addr + buffer.SizeBytes(); + if (buf_start_addr < end_addr && addr < buf_end_addr) { + return true; + } + page = Common::DivCeil(buf_end_addr, CACHING_PAGESIZE); + } + return false; +} + +bool BufferCache::IsRegionCpuModified(VAddr addr, size_t size) { + return memory_tracker.IsRegionCpuModified(addr, size); +} + +bool BufferCache::IsRegionGpuModified(VAddr addr, size_t size) { + return memory_tracker.IsRegionGpuModified(addr, size); +} + +BufferId BufferCache::FindBuffer(VAddr device_addr, u32 size) { + if (device_addr == 0) { + return NULL_BUFFER_ID; + } + const u64 page = device_addr >> CACHING_PAGEBITS; + const BufferId buffer_id = page_table[page]; + if (!buffer_id) { + return CreateBuffer(device_addr, size); + } + const Buffer& buffer = slot_buffers[buffer_id]; + if (buffer.IsInBounds(device_addr, size)) { + return buffer_id; + } + return CreateBuffer(device_addr, size); +} + +BufferCache::OverlapResult BufferCache::ResolveOverlaps(VAddr device_addr, u32 wanted_size) { + static constexpr int STREAM_LEAP_THRESHOLD = 16; + boost::container::small_vector overlap_ids; + VAddr begin = device_addr; + VAddr end = device_addr + wanted_size; + int stream_score = 0; + bool has_stream_leap = false; + const auto expand_begin = [&](VAddr add_value) { + static constexpr VAddr min_page = CACHING_PAGESIZE + DEVICE_PAGESIZE; + if (add_value > begin - min_page) { + begin = min_page; + device_addr = DEVICE_PAGESIZE; + return; + } + begin -= add_value; + device_addr = begin - CACHING_PAGESIZE; + }; + const auto expand_end = [&](VAddr add_value) { + static constexpr VAddr max_page = 1ULL << MemoryTracker::MAX_CPU_PAGE_BITS; + if (add_value > max_page - end) { + end = max_page; + return; + } + end += add_value; + }; + if (begin == 0) { + return OverlapResult{ + .ids = std::move(overlap_ids), + .begin = begin, + .end = end, + .has_stream_leap = has_stream_leap, + }; + } + for (; device_addr >> CACHING_PAGEBITS < Common::DivCeil(end, CACHING_PAGESIZE); + device_addr += CACHING_PAGESIZE) { + const BufferId overlap_id = page_table[device_addr >> CACHING_PAGEBITS]; + if (!overlap_id) { + continue; + } + Buffer& overlap = slot_buffers[overlap_id]; + if (overlap.is_picked) { + continue; + } + overlap_ids.push_back(overlap_id); + overlap.is_picked = true; + const VAddr overlap_device_addr = overlap.CpuAddr(); + const bool expands_left = overlap_device_addr < begin; + if (expands_left) { + begin = overlap_device_addr; + } + const VAddr overlap_end = overlap_device_addr + overlap.SizeBytes(); + const bool expands_right = overlap_end > end; + if (overlap_end > end) { + end = overlap_end; + } + stream_score += overlap.StreamScore(); + if (stream_score > STREAM_LEAP_THRESHOLD && !has_stream_leap) { + // When this memory region has been joined a bunch of times, we assume it's being used + // as a stream buffer. Increase the size to skip constantly recreating buffers. + has_stream_leap = true; + if (expands_right) { + expand_begin(CACHING_PAGESIZE * 128); + } + if (expands_left) { + expand_end(CACHING_PAGESIZE * 128); + } + } + } + return OverlapResult{ + .ids = std::move(overlap_ids), + .begin = begin, + .end = end, + .has_stream_leap = has_stream_leap, + }; +} + +void BufferCache::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, + bool accumulate_stream_score) { + Buffer& new_buffer = slot_buffers[new_buffer_id]; + Buffer& overlap = slot_buffers[overlap_id]; + if (accumulate_stream_score) { + new_buffer.IncreaseStreamScore(overlap.StreamScore() + 1); + } + const size_t dst_base_offset = overlap.CpuAddr() - new_buffer.CpuAddr(); + const vk::BufferCopy copy = { + .srcOffset = 0, + .dstOffset = dst_base_offset, + .size = overlap.SizeBytes(), + }; + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + static constexpr vk::MemoryBarrier READ_BARRIER{ + .srcAccessMask = vk::AccessFlagBits::eMemoryWrite, + .dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite, + }; + static constexpr vk::MemoryBarrier WRITE_BARRIER{ + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, + }; + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, + READ_BARRIER, {}, {}); + cmdbuf.copyBuffer(overlap.buffer, new_buffer.buffer, copy); + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eAllCommands, + vk::DependencyFlagBits::eByRegion, WRITE_BARRIER, {}, {}); + DeleteBuffer(overlap_id); +} + +BufferId BufferCache::CreateBuffer(VAddr device_addr, u32 wanted_size) { + const VAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE); + device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE); + wanted_size = static_cast(device_addr_end - device_addr); + const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size); + const u32 size = static_cast(overlap.end - overlap.begin); + const BufferId new_buffer_id = slot_buffers.insert( + instance, scheduler, MemoryUsage::DeviceLocal, overlap.begin, AllFlags, size); + auto& new_buffer = slot_buffers[new_buffer_id]; + const size_t size_bytes = new_buffer.SizeBytes(); + const auto cmdbuf = scheduler.CommandBuffer(); + scheduler.EndRendering(); + cmdbuf.fillBuffer(new_buffer.buffer, 0, size_bytes, 0); + for (const BufferId overlap_id : overlap.ids) { + JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap); + } + Register(new_buffer_id); + return new_buffer_id; +} + +void BufferCache::Register(BufferId buffer_id) { + ChangeRegister(buffer_id); +} + +void BufferCache::Unregister(BufferId buffer_id) { + ChangeRegister(buffer_id); +} + +template +void BufferCache::ChangeRegister(BufferId buffer_id) { + Buffer& buffer = slot_buffers[buffer_id]; + const auto size = buffer.SizeBytes(); + const VAddr device_addr_begin = buffer.CpuAddr(); + const VAddr device_addr_end = device_addr_begin + size; + const u64 page_begin = device_addr_begin / CACHING_PAGESIZE; + const u64 page_end = Common::DivCeil(device_addr_end, CACHING_PAGESIZE); + for (u64 page = page_begin; page != page_end; ++page) { + if constexpr (insert) { + page_table[page] = buffer_id; + } else { + page_table[page] = BufferId{}; + } + } +} + +void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, + bool is_texel_buffer) { + std::scoped_lock lk{mutex}; + boost::container::small_vector copies; + u64 total_size_bytes = 0; + u64 largest_copy = 0; + VAddr buffer_start = buffer.CpuAddr(); + memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) { + copies.push_back(vk::BufferCopy{ + .srcOffset = total_size_bytes, + .dstOffset = device_addr_out - buffer_start, + .size = range_size, + }); + total_size_bytes += range_size; + largest_copy = std::max(largest_copy, range_size); + }); + SCOPE_EXIT { + if (is_texel_buffer) { + SynchronizeBufferFromImage(buffer, device_addr, size); + } + }; + if (total_size_bytes == 0) { + return; + } + vk::Buffer src_buffer = staging_buffer.Handle(); + if (total_size_bytes < StagingBufferSize) { + const auto [staging, offset] = staging_buffer.Map(total_size_bytes); + for (auto& copy : copies) { + u8* const src_pointer = staging + copy.srcOffset; + const VAddr device_addr = buffer.CpuAddr() + copy.dstOffset; + std::memcpy(src_pointer, std::bit_cast(device_addr), copy.size); + // Apply the staging offset + copy.srcOffset += offset; + } + staging_buffer.Commit(); + } else { + // For large one time transfers use a temporary host buffer. + // RenderDoc can lag quite a bit if the stream buffer is too large. + Buffer temp_buffer{instance, + scheduler, + MemoryUsage::Upload, + 0, + vk::BufferUsageFlagBits::eTransferSrc, + total_size_bytes}; + src_buffer = temp_buffer.Handle(); + u8* const staging = temp_buffer.mapped_data.data(); + for (auto& copy : copies) { + u8* const src_pointer = staging + copy.srcOffset; + const VAddr device_addr = buffer.CpuAddr() + copy.dstOffset; + std::memcpy(src_pointer, std::bit_cast(device_addr), copy.size); + } + scheduler.DeferOperation([buffer = std::move(temp_buffer)]() mutable {}); + } + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + static constexpr vk::MemoryBarrier READ_BARRIER{ + .srcAccessMask = vk::AccessFlagBits::eMemoryWrite, + .dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite, + }; + static constexpr vk::MemoryBarrier WRITE_BARRIER{ + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, + }; + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, + READ_BARRIER, {}, {}); + cmdbuf.copyBuffer(src_buffer, buffer.buffer, copies); + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eAllCommands, + vk::DependencyFlagBits::eByRegion, WRITE_BARRIER, {}, {}); +} + +bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size) { + static constexpr FindFlags find_flags = + FindFlags::NoCreate | FindFlags::RelaxDim | FindFlags::RelaxFmt | FindFlags::RelaxSize; + TextureCache::BaseDesc desc{}; + desc.info.guest_address = device_addr; + desc.info.guest_size_bytes = size; + const ImageId image_id = texture_cache.FindImage(desc, find_flags); + if (!image_id) { + return false; + } + Image& image = texture_cache.GetImage(image_id); + if (False(image.flags & ImageFlagBits::GpuModified)) { + return false; + } + ASSERT_MSG(device_addr == image.info.guest_address, + "Texel buffer aliases image subresources {:x} : {:x}", device_addr, + image.info.guest_address); + boost::container::small_vector copies; + u32 offset = buffer.Offset(image.info.guest_address); + const u32 num_layers = image.info.resources.layers; + const u32 max_offset = offset + size; + for (u32 m = 0; m < image.info.resources.levels; m++) { + const u32 width = std::max(image.info.size.width >> m, 1u); + const u32 height = std::max(image.info.size.height >> m, 1u); + const u32 depth = + image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; + const auto& [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; + offset += mip_ofs * num_layers; + if (offset + (mip_size * num_layers) > max_offset) { + break; + } + copies.push_back({ + .bufferOffset = offset, + .bufferRowLength = static_cast(mip_pitch), + .bufferImageHeight = static_cast(mip_height), + .imageSubresource{ + .aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, + .mipLevel = m, + .baseArrayLayer = 0, + .layerCount = num_layers, + }, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, depth}, + }); + } + if (!copies.empty()) { + scheduler.EndRendering(); + image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.copyImageToBuffer(image.image, vk::ImageLayout::eTransferSrcOptimal, buffer.buffer, + copies); + } + return true; +} + +void BufferCache::DeleteBuffer(BufferId buffer_id) { + Buffer& buffer = slot_buffers[buffer_id]; + Unregister(buffer_id); + scheduler.DeferOperation([this, buffer_id] { slot_buffers.erase(buffer_id); }); + buffer.is_deleted = true; +} + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index e62913413..bcbaa45dc 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1,168 +1,168 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include "common/div_ceil.h" -#include "common/slot_vector.h" -#include "common/types.h" -#include "video_core/buffer_cache/buffer.h" -#include "video_core/buffer_cache/memory_tracker_base.h" -#include "video_core/buffer_cache/range_set.h" -#include "video_core/multi_level_page_table.h" - -namespace AmdGpu { -struct Liverpool; -} - -namespace Shader { -namespace Gcn { -struct FetchShaderData; -} -struct Info; -} // namespace Shader - -namespace VideoCore { - -using BufferId = Common::SlotId; - -static constexpr BufferId NULL_BUFFER_ID{0}; - -class TextureCache; - -class BufferCache { -public: - static constexpr u32 CACHING_PAGEBITS = 12; - static constexpr u64 CACHING_PAGESIZE = u64{1} << CACHING_PAGEBITS; - static constexpr u64 DEVICE_PAGESIZE = 4_KB; - - struct Traits { - using Entry = BufferId; - static constexpr size_t AddressSpaceBits = 40; - static constexpr size_t FirstLevelBits = 14; - static constexpr size_t PageBits = CACHING_PAGEBITS; - }; - using PageTable = MultiLevelPageTable; - - struct OverlapResult { - boost::container::small_vector ids; - VAddr begin; - VAddr end; - bool has_stream_leap = false; - }; - -public: - explicit BufferCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, - AmdGpu::Liverpool* liverpool, TextureCache& texture_cache, - PageManager& tracker); - ~BufferCache(); - - /// Returns a pointer to GDS device local buffer. - [[nodiscard]] const Buffer* GetGdsBuffer() const noexcept { - return &gds_buffer; - } - - /// Retrieves the buffer with the specified id. - [[nodiscard]] Buffer& GetBuffer(BufferId id) { - return slot_buffers[id]; - } - - [[nodiscard]] vk::BufferView& NullBufferView() { - return null_buffer_view; - } - - /// Invalidates any buffer in the logical page range. - void InvalidateMemory(VAddr device_addr, u64 size); - - /// Binds host vertex buffers for the current draw. - bool BindVertexBuffers(const Shader::Info& vs_info, - const std::optional& fetch_shader); - - /// Bind host index buffer for the current draw. - u32 BindIndexBuffer(bool& is_indexed, u32 index_offset); - - /// Writes a value to GPU buffer. - void InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds); - - [[nodiscard]] std::pair ObtainHostUBO(std::span data); - - /// Obtains a buffer for the specified region. - [[nodiscard]] std::pair ObtainBuffer(VAddr gpu_addr, u32 size, bool is_written, - bool is_texel_buffer = false, - BufferId buffer_id = {}); - - /// Attempts to obtain a buffer without modifying the cache contents. - [[nodiscard]] std::pair ObtainViewBuffer(VAddr gpu_addr, u32 size, - bool prefer_gpu); - - /// Return true when a region is registered on the cache - [[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size); - - /// Return true when a CPU region is modified from the CPU - [[nodiscard]] bool IsRegionCpuModified(VAddr addr, size_t size); - - /// Return true when a CPU region is modified from the GPU - [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size); - - [[nodiscard]] BufferId FindBuffer(VAddr device_addr, u32 size); - -private: - template - void ForEachBufferInRange(VAddr device_addr, u64 size, Func&& func) { - const u64 page_end = Common::DivCeil(device_addr + size, CACHING_PAGESIZE); - for (u64 page = device_addr >> CACHING_PAGEBITS; page < page_end;) { - const BufferId buffer_id = page_table[page]; - if (!buffer_id) { - ++page; - continue; - } - Buffer& buffer = slot_buffers[buffer_id]; - func(buffer_id, buffer); - - const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); - page = Common::DivCeil(end_addr, CACHING_PAGESIZE); - } - } - - void DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size); - - [[nodiscard]] OverlapResult ResolveOverlaps(VAddr device_addr, u32 wanted_size); - - void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score); - - [[nodiscard]] BufferId CreateBuffer(VAddr device_addr, u32 wanted_size); - - void Register(BufferId buffer_id); - - void Unregister(BufferId buffer_id); - - template - void ChangeRegister(BufferId buffer_id); - - void SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, bool is_texel_buffer); - - bool SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size); - - void DeleteBuffer(BufferId buffer_id); - - const Vulkan::Instance& instance; - Vulkan::Scheduler& scheduler; - AmdGpu::Liverpool* liverpool; - TextureCache& texture_cache; - PageManager& tracker; - StreamBuffer staging_buffer; - StreamBuffer stream_buffer; - Buffer gds_buffer; - std::mutex mutex; - Common::SlotVector slot_buffers; - RangeSet gpu_modified_ranges; - vk::BufferView null_buffer_view; - MemoryTracker memory_tracker; - PageTable page_table; -}; - -} // namespace VideoCore +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include "common/div_ceil.h" +#include "common/slot_vector.h" +#include "common/types.h" +#include "video_core/buffer_cache/buffer.h" +#include "video_core/buffer_cache/memory_tracker_base.h" +#include "video_core/buffer_cache/range_set.h" +#include "video_core/multi_level_page_table.h" + +namespace AmdGpu { +struct Liverpool; +} + +namespace Shader { +namespace Gcn { +struct FetchShaderData; +} +struct Info; +} // namespace Shader + +namespace VideoCore { + +using BufferId = Common::SlotId; + +static constexpr BufferId NULL_BUFFER_ID{0}; + +class TextureCache; + +class BufferCache { +public: + static constexpr u32 CACHING_PAGEBITS = 12; + static constexpr u64 CACHING_PAGESIZE = u64{1} << CACHING_PAGEBITS; + static constexpr u64 DEVICE_PAGESIZE = 4_KB; + + struct Traits { + using Entry = BufferId; + static constexpr size_t AddressSpaceBits = 40; + static constexpr size_t FirstLevelBits = 14; + static constexpr size_t PageBits = CACHING_PAGEBITS; + }; + using PageTable = MultiLevelPageTable; + + struct OverlapResult { + boost::container::small_vector ids; + VAddr begin; + VAddr end; + bool has_stream_leap = false; + }; + +public: + explicit BufferCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, + AmdGpu::Liverpool* liverpool, TextureCache& texture_cache, + PageManager& tracker); + ~BufferCache(); + + /// Returns a pointer to GDS device local buffer. + [[nodiscard]] const Buffer* GetGdsBuffer() const noexcept { + return &gds_buffer; + } + + /// Retrieves the buffer with the specified id. + [[nodiscard]] Buffer& GetBuffer(BufferId id) { + return slot_buffers[id]; + } + + [[nodiscard]] vk::BufferView& NullBufferView() { + return null_buffer_view; + } + + /// Invalidates any buffer in the logical page range. + void InvalidateMemory(VAddr device_addr, u64 size); + + /// Binds host vertex buffers for the current draw. + bool BindVertexBuffers(const Shader::Info& vs_info, + const std::optional& fetch_shader); + + /// Bind host index buffer for the current draw. + u32 BindIndexBuffer(bool& is_indexed, u32 index_offset); + + /// Writes a value to GPU buffer. + void InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds); + + [[nodiscard]] std::pair ObtainHostUBO(std::span data); + + /// Obtains a buffer for the specified region. + [[nodiscard]] std::pair ObtainBuffer(VAddr gpu_addr, u32 size, bool is_written, + bool is_texel_buffer = false, + BufferId buffer_id = {}); + + /// Attempts to obtain a buffer without modifying the cache contents. + [[nodiscard]] std::pair ObtainViewBuffer(VAddr gpu_addr, u32 size, + bool prefer_gpu); + + /// Return true when a region is registered on the cache + [[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size); + + /// Return true when a CPU region is modified from the CPU + [[nodiscard]] bool IsRegionCpuModified(VAddr addr, size_t size); + + /// Return true when a CPU region is modified from the GPU + [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size); + + [[nodiscard]] BufferId FindBuffer(VAddr device_addr, u32 size); + +private: + template + void ForEachBufferInRange(VAddr device_addr, u64 size, Func&& func) { + const u64 page_end = Common::DivCeil(device_addr + size, CACHING_PAGESIZE); + for (u64 page = device_addr >> CACHING_PAGEBITS; page < page_end;) { + const BufferId buffer_id = page_table[page]; + if (!buffer_id) { + ++page; + continue; + } + Buffer& buffer = slot_buffers[buffer_id]; + func(buffer_id, buffer); + + const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); + page = Common::DivCeil(end_addr, CACHING_PAGESIZE); + } + } + + void DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size); + + [[nodiscard]] OverlapResult ResolveOverlaps(VAddr device_addr, u32 wanted_size); + + void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score); + + [[nodiscard]] BufferId CreateBuffer(VAddr device_addr, u32 wanted_size); + + void Register(BufferId buffer_id); + + void Unregister(BufferId buffer_id); + + template + void ChangeRegister(BufferId buffer_id); + + void SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, bool is_texel_buffer); + + bool SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size); + + void DeleteBuffer(BufferId buffer_id); + + const Vulkan::Instance& instance; + Vulkan::Scheduler& scheduler; + AmdGpu::Liverpool* liverpool; + TextureCache& texture_cache; + PageManager& tracker; + StreamBuffer staging_buffer; + StreamBuffer stream_buffer; + Buffer gds_buffer; + std::mutex mutex; + Common::SlotVector slot_buffers; + RangeSet gpu_modified_ranges; + vk::BufferView null_buffer_view; + MemoryTracker memory_tracker; + PageTable page_table; +}; + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h index a59bcfff5..ae61b55f2 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker_base.h @@ -1,175 +1,175 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include "common/types.h" -#include "video_core/buffer_cache/word_manager.h" - -namespace VideoCore { - -class MemoryTracker { -public: - static constexpr size_t MAX_CPU_PAGE_BITS = 40; - static constexpr size_t HIGHER_PAGE_BITS = 22; - static constexpr size_t HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS; - static constexpr size_t HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL; - static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - HIGHER_PAGE_BITS); - static constexpr size_t MANAGER_POOL_SIZE = 32; - static constexpr size_t WORDS_STACK_NEEDED = HIGHER_PAGE_SIZE / BYTES_PER_WORD; - using Manager = WordManager; - -public: - explicit MemoryTracker(PageManager* tracker_) : tracker{tracker_} {} - ~MemoryTracker() = default; - - /// Returns true if a region has been modified from the CPU - [[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { - return IteratePages( - query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) { - return manager->template IsRegionModified(offset, size); - }); - } - - /// Returns true if a region has been modified from the GPU - [[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { - return IteratePages( - query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) { - return manager->template IsRegionModified(offset, size); - }); - } - - /// Mark region as CPU modified, notifying the device_tracker about this change - void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) { - IteratePages(dirty_cpu_addr, query_size, - [](Manager* manager, u64 offset, size_t size) { - manager->template ChangeRegionState( - manager->GetCpuAddr() + offset, size); - }); - } - - /// Unmark region as CPU modified, notifying the device_tracker about this change - void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) { - IteratePages(dirty_cpu_addr, query_size, - [](Manager* manager, u64 offset, size_t size) { - manager->template ChangeRegionState( - manager->GetCpuAddr() + offset, size); - }); - } - - /// Mark region as modified from the host GPU - void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept { - IteratePages(dirty_cpu_addr, query_size, - [](Manager* manager, u64 offset, size_t size) { - manager->template ChangeRegionState( - manager->GetCpuAddr() + offset, size); - }); - } - - /// Unmark region as modified from the host GPU - void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept { - IteratePages(dirty_cpu_addr, query_size, - [](Manager* manager, u64 offset, size_t size) { - manager->template ChangeRegionState( - manager->GetCpuAddr() + offset, size); - }); - } - - /// Call 'func' for each CPU modified range and unmark those pages as CPU modified - template - void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, Func&& func) { - IteratePages(query_cpu_range, query_size, - [&func](Manager* manager, u64 offset, size_t size) { - manager->template ForEachModifiedRange( - manager->GetCpuAddr() + offset, size, func); - }); - } - - /// Call 'func' for each GPU modified range and unmark those pages as GPU modified - template - void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, Func&& func) { - IteratePages(query_cpu_range, query_size, - [&func](Manager* manager, u64 offset, size_t size) { - if constexpr (clear) { - manager->template ForEachModifiedRange( - manager->GetCpuAddr() + offset, size, func); - } else { - manager->template ForEachModifiedRange( - manager->GetCpuAddr() + offset, size, func); - } - }); - } - -private: - /** - * @brief IteratePages Iterates L2 word manager page table. - * @param cpu_address Start byte cpu address - * @param size Size in bytes of the region of iterate. - * @param func Callback for each word manager. - * @return - */ - template - bool IteratePages(VAddr cpu_address, size_t size, Func&& func) { - using FuncReturn = typename std::invoke_result::type; - static constexpr bool BOOL_BREAK = std::is_same_v; - std::size_t remaining_size{size}; - std::size_t page_index{cpu_address >> HIGHER_PAGE_BITS}; - u64 page_offset{cpu_address & HIGHER_PAGE_MASK}; - while (remaining_size > 0) { - const std::size_t copy_amount{ - std::min(HIGHER_PAGE_SIZE - page_offset, remaining_size)}; - auto* manager{top_tier[page_index]}; - if (manager) { - if constexpr (BOOL_BREAK) { - if (func(manager, page_offset, copy_amount)) { - return true; - } - } else { - func(manager, page_offset, copy_amount); - } - } else if constexpr (create_region_on_fail) { - CreateRegion(page_index); - manager = top_tier[page_index]; - if constexpr (BOOL_BREAK) { - if (func(manager, page_offset, copy_amount)) { - return true; - } - } else { - func(manager, page_offset, copy_amount); - } - } - page_index++; - page_offset = 0; - remaining_size -= copy_amount; - } - return false; - } - - void CreateRegion(std::size_t page_index) { - const VAddr base_cpu_addr = page_index << HIGHER_PAGE_BITS; - if (free_managers.empty()) { - manager_pool.emplace_back(); - auto& last_pool = manager_pool.back(); - for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) { - std::construct_at(&last_pool[i], tracker, 0, HIGHER_PAGE_SIZE); - free_managers.push_back(&last_pool[i]); - } - } - // Each manager tracks a 4_MB virtual address space. - auto* new_manager = free_managers.back(); - new_manager->SetCpuAddress(base_cpu_addr); - free_managers.pop_back(); - top_tier[page_index] = new_manager; - } - - PageManager* tracker; - std::deque> manager_pool; - std::vector free_managers; - std::array top_tier{}; -}; - -} // namespace VideoCore +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include "common/types.h" +#include "video_core/buffer_cache/word_manager.h" + +namespace VideoCore { + +class MemoryTracker { +public: + static constexpr size_t MAX_CPU_PAGE_BITS = 40; + static constexpr size_t HIGHER_PAGE_BITS = 22; + static constexpr size_t HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS; + static constexpr size_t HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL; + static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - HIGHER_PAGE_BITS); + static constexpr size_t MANAGER_POOL_SIZE = 32; + static constexpr size_t WORDS_STACK_NEEDED = HIGHER_PAGE_SIZE / BYTES_PER_WORD; + using Manager = WordManager; + +public: + explicit MemoryTracker(PageManager* tracker_) : tracker{tracker_} {} + ~MemoryTracker() = default; + + /// Returns true if a region has been modified from the CPU + [[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { + return IteratePages( + query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) { + return manager->template IsRegionModified(offset, size); + }); + } + + /// Returns true if a region has been modified from the GPU + [[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { + return IteratePages( + query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) { + return manager->template IsRegionModified(offset, size); + }); + } + + /// Mark region as CPU modified, notifying the device_tracker about this change + void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) { + IteratePages(dirty_cpu_addr, query_size, + [](Manager* manager, u64 offset, size_t size) { + manager->template ChangeRegionState( + manager->GetCpuAddr() + offset, size); + }); + } + + /// Unmark region as CPU modified, notifying the device_tracker about this change + void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) { + IteratePages(dirty_cpu_addr, query_size, + [](Manager* manager, u64 offset, size_t size) { + manager->template ChangeRegionState( + manager->GetCpuAddr() + offset, size); + }); + } + + /// Mark region as modified from the host GPU + void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept { + IteratePages(dirty_cpu_addr, query_size, + [](Manager* manager, u64 offset, size_t size) { + manager->template ChangeRegionState( + manager->GetCpuAddr() + offset, size); + }); + } + + /// Unmark region as modified from the host GPU + void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept { + IteratePages(dirty_cpu_addr, query_size, + [](Manager* manager, u64 offset, size_t size) { + manager->template ChangeRegionState( + manager->GetCpuAddr() + offset, size); + }); + } + + /// Call 'func' for each CPU modified range and unmark those pages as CPU modified + template + void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, Func&& func) { + IteratePages(query_cpu_range, query_size, + [&func](Manager* manager, u64 offset, size_t size) { + manager->template ForEachModifiedRange( + manager->GetCpuAddr() + offset, size, func); + }); + } + + /// Call 'func' for each GPU modified range and unmark those pages as GPU modified + template + void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, Func&& func) { + IteratePages(query_cpu_range, query_size, + [&func](Manager* manager, u64 offset, size_t size) { + if constexpr (clear) { + manager->template ForEachModifiedRange( + manager->GetCpuAddr() + offset, size, func); + } else { + manager->template ForEachModifiedRange( + manager->GetCpuAddr() + offset, size, func); + } + }); + } + +private: + /** + * @brief IteratePages Iterates L2 word manager page table. + * @param cpu_address Start byte cpu address + * @param size Size in bytes of the region of iterate. + * @param func Callback for each word manager. + * @return + */ + template + bool IteratePages(VAddr cpu_address, size_t size, Func&& func) { + using FuncReturn = typename std::invoke_result::type; + static constexpr bool BOOL_BREAK = std::is_same_v; + std::size_t remaining_size{size}; + std::size_t page_index{cpu_address >> HIGHER_PAGE_BITS}; + u64 page_offset{cpu_address & HIGHER_PAGE_MASK}; + while (remaining_size > 0) { + const std::size_t copy_amount{ + std::min(HIGHER_PAGE_SIZE - page_offset, remaining_size)}; + auto* manager{top_tier[page_index]}; + if (manager) { + if constexpr (BOOL_BREAK) { + if (func(manager, page_offset, copy_amount)) { + return true; + } + } else { + func(manager, page_offset, copy_amount); + } + } else if constexpr (create_region_on_fail) { + CreateRegion(page_index); + manager = top_tier[page_index]; + if constexpr (BOOL_BREAK) { + if (func(manager, page_offset, copy_amount)) { + return true; + } + } else { + func(manager, page_offset, copy_amount); + } + } + page_index++; + page_offset = 0; + remaining_size -= copy_amount; + } + return false; + } + + void CreateRegion(std::size_t page_index) { + const VAddr base_cpu_addr = page_index << HIGHER_PAGE_BITS; + if (free_managers.empty()) { + manager_pool.emplace_back(); + auto& last_pool = manager_pool.back(); + for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) { + std::construct_at(&last_pool[i], tracker, 0, HIGHER_PAGE_SIZE); + free_managers.push_back(&last_pool[i]); + } + } + // Each manager tracks a 4_MB virtual address space. + auto* new_manager = free_managers.back(); + new_manager->SetCpuAddress(base_cpu_addr); + free_managers.pop_back(); + top_tier[page_index] = new_manager; + } + + PageManager* tracker; + std::deque> manager_pool; + std::vector free_managers; + std::array top_tier{}; +}; + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h index 549d2a9ed..ae85d1eb1 100644 --- a/src/video_core/buffer_cache/word_manager.h +++ b/src/video_core/buffer_cache/word_manager.h @@ -1,398 +1,398 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include "common/div_ceil.h" -#include "common/types.h" -#include "video_core/page_manager.h" - -namespace VideoCore { - -constexpr u64 PAGES_PER_WORD = 64; -constexpr u64 BYTES_PER_PAGE = 4_KB; -constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE; - -enum class Type { - CPU, - GPU, - Untracked, -}; - -/// Vector tracking modified pages tightly packed with small vector optimization -template -struct WordsArray { - /// Returns the pointer to the words state - [[nodiscard]] const u64* Pointer(bool is_short) const noexcept { - return is_short ? stack.data() : heap; - } - - /// Returns the pointer to the words state - [[nodiscard]] u64* Pointer(bool is_short) noexcept { - return is_short ? stack.data() : heap; - } - - std::array stack{}; ///< Small buffers storage - u64* heap; ///< Not-small buffers pointer to the storage -}; - -template -struct Words { - explicit Words() = default; - explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} { - num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD); - if (IsShort()) { - cpu.stack.fill(~u64{0}); - gpu.stack.fill(0); - untracked.stack.fill(~u64{0}); - } else { - // Share allocation between CPU and GPU pages and set their default values - u64* const alloc = new u64[num_words * 3]; - cpu.heap = alloc; - gpu.heap = alloc + num_words; - untracked.heap = alloc + num_words * 2; - std::fill_n(cpu.heap, num_words, ~u64{0}); - std::fill_n(gpu.heap, num_words, 0); - std::fill_n(untracked.heap, num_words, ~u64{0}); - } - // Clean up tailing bits - const u64 last_word_size = size_bytes % BYTES_PER_WORD; - const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE); - const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD; - const u64 last_word = (~u64{0} << shift) >> shift; - cpu.Pointer(IsShort())[NumWords() - 1] = last_word; - untracked.Pointer(IsShort())[NumWords() - 1] = last_word; - } - - ~Words() { - Release(); - } - - Words& operator=(Words&& rhs) noexcept { - Release(); - size_bytes = rhs.size_bytes; - num_words = rhs.num_words; - cpu = rhs.cpu; - gpu = rhs.gpu; - untracked = rhs.untracked; - rhs.cpu.heap = nullptr; - return *this; - } - - Words(Words&& rhs) noexcept - : size_bytes{rhs.size_bytes}, num_words{rhs.num_words}, cpu{rhs.cpu}, gpu{rhs.gpu}, - untracked{rhs.untracked} { - rhs.cpu.heap = nullptr; - } - - Words& operator=(const Words&) = delete; - Words(const Words&) = delete; - - /// Returns true when the buffer fits in the small vector optimization - [[nodiscard]] bool IsShort() const noexcept { - return num_words <= stack_words; - } - - /// Returns the number of words of the buffer - [[nodiscard]] size_t NumWords() const noexcept { - return num_words; - } - - /// Release buffer resources - void Release() { - if (!IsShort()) { - // CPU written words is the base for the heap allocation - delete[] cpu.heap; - } - } - - template - std::span Span() noexcept { - if constexpr (type == Type::CPU) { - return std::span(cpu.Pointer(IsShort()), num_words); - } else if constexpr (type == Type::GPU) { - return std::span(gpu.Pointer(IsShort()), num_words); - } else if constexpr (type == Type::Untracked) { - return std::span(untracked.Pointer(IsShort()), num_words); - } - } - - template - std::span Span() const noexcept { - if constexpr (type == Type::CPU) { - return std::span(cpu.Pointer(IsShort()), num_words); - } else if constexpr (type == Type::GPU) { - return std::span(gpu.Pointer(IsShort()), num_words); - } else if constexpr (type == Type::Untracked) { - return std::span(untracked.Pointer(IsShort()), num_words); - } - } - - u64 size_bytes = 0; - size_t num_words = 0; - WordsArray cpu; - WordsArray gpu; - WordsArray untracked; -}; - -template -class WordManager { -public: - explicit WordManager(PageManager* tracker_, VAddr cpu_addr_, u64 size_bytes) - : tracker{tracker_}, cpu_addr{cpu_addr_}, words{size_bytes} {} - - explicit WordManager() = default; - - void SetCpuAddress(VAddr new_cpu_addr) { - cpu_addr = new_cpu_addr; - } - - VAddr GetCpuAddr() const { - return cpu_addr; - } - - static u64 ExtractBits(u64 word, size_t page_start, size_t page_end) { - constexpr size_t number_bits = sizeof(u64) * 8; - const size_t limit_page_end = number_bits - std::min(page_end, number_bits); - u64 bits = (word >> page_start) << page_start; - bits = (bits << limit_page_end) >> limit_page_end; - return bits; - } - - static std::pair GetWordPage(VAddr address) { - const size_t converted_address = static_cast(address); - const size_t word_number = converted_address / BYTES_PER_WORD; - const size_t amount_pages = converted_address % BYTES_PER_WORD; - return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE); - } - - template - void IterateWords(size_t offset, size_t size, Func&& func) const { - using FuncReturn = std::invoke_result_t; - static constexpr bool BOOL_BREAK = std::is_same_v; - const size_t start = static_cast(std::max(static_cast(offset), 0LL)); - const size_t end = static_cast(std::max(static_cast(offset + size), 0LL)); - if (start >= SizeBytes() || end <= start) { - return; - } - auto [start_word, start_page] = GetWordPage(start); - auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL); - const size_t num_words = NumWords(); - start_word = std::min(start_word, num_words); - end_word = std::min(end_word, num_words); - const size_t diff = end_word - start_word; - end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD; - end_word = std::min(end_word, num_words); - end_page += diff * PAGES_PER_WORD; - constexpr u64 base_mask{~0ULL}; - for (size_t word_index = start_word; word_index < end_word; word_index++) { - const u64 mask = ExtractBits(base_mask, start_page, end_page); - start_page = 0; - end_page -= PAGES_PER_WORD; - if constexpr (BOOL_BREAK) { - if (func(word_index, mask)) { - return; - } - } else { - func(word_index, mask); - } - } - } - - template - void IteratePages(u64 mask, Func&& func) const { - size_t offset = 0; - while (mask != 0) { - const size_t empty_bits = std::countr_zero(mask); - offset += empty_bits; - mask = mask >> empty_bits; - - const size_t continuous_bits = std::countr_one(mask); - func(offset, continuous_bits); - mask = continuous_bits < PAGES_PER_WORD ? (mask >> continuous_bits) : 0; - offset += continuous_bits; - } - } - - /** - * Change the state of a range of pages - * - * @param dirty_addr Base address to mark or unmark as modified - * @param size Size in bytes to mark or unmark as modified - */ - template - void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) { - std::span state_words = words.template Span(); - [[maybe_unused]] std::span untracked_words = words.template Span(); - IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) { - if constexpr (type == Type::CPU) { - NotifyPageTracker(index, untracked_words[index], mask); - } - if constexpr (enable) { - state_words[index] |= mask; - if constexpr (type == Type::CPU) { - untracked_words[index] |= mask; - } - } else { - state_words[index] &= ~mask; - if constexpr (type == Type::CPU) { - untracked_words[index] &= ~mask; - } - } - }); - } - - /** - * Loop over each page in the given range, turn off those bits and notify the tracker if - * needed. Call the given function on each turned off range. - * - * @param query_cpu_range Base CPU address to loop over - * @param size Size in bytes of the CPU range to loop over - * @param func Function to call for each turned off region - */ - template - void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) { - static_assert(type != Type::Untracked); - - std::span state_words = words.template Span(); - [[maybe_unused]] std::span untracked_words = words.template Span(); - const size_t offset = query_cpu_range - cpu_addr; - bool pending = false; - size_t pending_offset{}; - size_t pending_pointer{}; - const auto release = [&]() { - func(cpu_addr + pending_offset * BYTES_PER_PAGE, - (pending_pointer - pending_offset) * BYTES_PER_PAGE); - }; - IterateWords(offset, size, [&](size_t index, u64 mask) { - if constexpr (type == Type::GPU) { - mask &= ~untracked_words[index]; - } - const u64 word = state_words[index] & mask; - if constexpr (clear) { - if constexpr (type == Type::CPU) { - NotifyPageTracker(index, untracked_words[index], mask); - } - state_words[index] &= ~mask; - if constexpr (type == Type::CPU) { - untracked_words[index] &= ~mask; - } - } - const size_t base_offset = index * PAGES_PER_WORD; - IteratePages(word, [&](size_t pages_offset, size_t pages_size) { - const auto reset = [&]() { - pending_offset = base_offset + pages_offset; - pending_pointer = base_offset + pages_offset + pages_size; - }; - if (!pending) { - reset(); - pending = true; - return; - } - if (pending_pointer == base_offset + pages_offset) { - pending_pointer += pages_size; - return; - } - release(); - reset(); - }); - }); - if (pending) { - release(); - } - } - - /** - * Returns true when a region has been modified - * - * @param offset Offset in bytes from the start of the buffer - * @param size Size in bytes of the region to query for modifications - */ - template - [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept { - static_assert(type != Type::Untracked); - - const std::span state_words = words.template Span(); - [[maybe_unused]] const std::span untracked_words = - words.template Span(); - bool result = false; - IterateWords(offset, size, [&](size_t index, u64 mask) { - if constexpr (type == Type::GPU) { - mask &= ~untracked_words[index]; - } - const u64 word = state_words[index] & mask; - if (word != 0) { - result = true; - return true; - } - return false; - }); - return result; - } - - /// Returns the number of words of the manager - [[nodiscard]] size_t NumWords() const noexcept { - return words.NumWords(); - } - - /// Returns the size in bytes of the manager - [[nodiscard]] u64 SizeBytes() const noexcept { - return words.size_bytes; - } - - /// Returns true when the buffer fits in the small vector optimization - [[nodiscard]] bool IsShort() const noexcept { - return words.IsShort(); - } - -private: - template - u64* Array() noexcept { - if constexpr (type == Type::CPU) { - return words.cpu.Pointer(IsShort()); - } else if constexpr (type == Type::GPU) { - return words.gpu.Pointer(IsShort()); - } else if constexpr (type == Type::Untracked) { - return words.untracked.Pointer(IsShort()); - } - } - - template - const u64* Array() const noexcept { - if constexpr (type == Type::CPU) { - return words.cpu.Pointer(IsShort()); - } else if constexpr (type == Type::GPU) { - return words.gpu.Pointer(IsShort()); - } else if constexpr (type == Type::Untracked) { - return words.untracked.Pointer(IsShort()); - } - } - - /** - * Notify tracker about changes in the CPU tracking state of a word in the buffer - * - * @param word_index Index to the word to notify to the tracker - * @param current_bits Current state of the word - * @param new_bits New state of the word - * - * @tparam add_to_tracker True when the tracker should start tracking the new pages - */ - template - void NotifyPageTracker(u64 word_index, u64 current_bits, u64 new_bits) const { - u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits; - VAddr addr = cpu_addr + word_index * BYTES_PER_WORD; - IteratePages(changed_bits, [&](size_t offset, size_t size) { - tracker->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE, - add_to_tracker ? 1 : -1); - }); - } - - PageManager* tracker; - VAddr cpu_addr = 0; - Words words; -}; - -} // namespace VideoCore +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "common/div_ceil.h" +#include "common/types.h" +#include "video_core/page_manager.h" + +namespace VideoCore { + +constexpr u64 PAGES_PER_WORD = 64; +constexpr u64 BYTES_PER_PAGE = 4_KB; +constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE; + +enum class Type { + CPU, + GPU, + Untracked, +}; + +/// Vector tracking modified pages tightly packed with small vector optimization +template +struct WordsArray { + /// Returns the pointer to the words state + [[nodiscard]] const u64* Pointer(bool is_short) const noexcept { + return is_short ? stack.data() : heap; + } + + /// Returns the pointer to the words state + [[nodiscard]] u64* Pointer(bool is_short) noexcept { + return is_short ? stack.data() : heap; + } + + std::array stack{}; ///< Small buffers storage + u64* heap; ///< Not-small buffers pointer to the storage +}; + +template +struct Words { + explicit Words() = default; + explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} { + num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD); + if (IsShort()) { + cpu.stack.fill(~u64{0}); + gpu.stack.fill(0); + untracked.stack.fill(~u64{0}); + } else { + // Share allocation between CPU and GPU pages and set their default values + u64* const alloc = new u64[num_words * 3]; + cpu.heap = alloc; + gpu.heap = alloc + num_words; + untracked.heap = alloc + num_words * 2; + std::fill_n(cpu.heap, num_words, ~u64{0}); + std::fill_n(gpu.heap, num_words, 0); + std::fill_n(untracked.heap, num_words, ~u64{0}); + } + // Clean up tailing bits + const u64 last_word_size = size_bytes % BYTES_PER_WORD; + const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE); + const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD; + const u64 last_word = (~u64{0} << shift) >> shift; + cpu.Pointer(IsShort())[NumWords() - 1] = last_word; + untracked.Pointer(IsShort())[NumWords() - 1] = last_word; + } + + ~Words() { + Release(); + } + + Words& operator=(Words&& rhs) noexcept { + Release(); + size_bytes = rhs.size_bytes; + num_words = rhs.num_words; + cpu = rhs.cpu; + gpu = rhs.gpu; + untracked = rhs.untracked; + rhs.cpu.heap = nullptr; + return *this; + } + + Words(Words&& rhs) noexcept + : size_bytes{rhs.size_bytes}, num_words{rhs.num_words}, cpu{rhs.cpu}, gpu{rhs.gpu}, + untracked{rhs.untracked} { + rhs.cpu.heap = nullptr; + } + + Words& operator=(const Words&) = delete; + Words(const Words&) = delete; + + /// Returns true when the buffer fits in the small vector optimization + [[nodiscard]] bool IsShort() const noexcept { + return num_words <= stack_words; + } + + /// Returns the number of words of the buffer + [[nodiscard]] size_t NumWords() const noexcept { + return num_words; + } + + /// Release buffer resources + void Release() { + if (!IsShort()) { + // CPU written words is the base for the heap allocation + delete[] cpu.heap; + } + } + + template + std::span Span() noexcept { + if constexpr (type == Type::CPU) { + return std::span(cpu.Pointer(IsShort()), num_words); + } else if constexpr (type == Type::GPU) { + return std::span(gpu.Pointer(IsShort()), num_words); + } else if constexpr (type == Type::Untracked) { + return std::span(untracked.Pointer(IsShort()), num_words); + } + } + + template + std::span Span() const noexcept { + if constexpr (type == Type::CPU) { + return std::span(cpu.Pointer(IsShort()), num_words); + } else if constexpr (type == Type::GPU) { + return std::span(gpu.Pointer(IsShort()), num_words); + } else if constexpr (type == Type::Untracked) { + return std::span(untracked.Pointer(IsShort()), num_words); + } + } + + u64 size_bytes = 0; + size_t num_words = 0; + WordsArray cpu; + WordsArray gpu; + WordsArray untracked; +}; + +template +class WordManager { +public: + explicit WordManager(PageManager* tracker_, VAddr cpu_addr_, u64 size_bytes) + : tracker{tracker_}, cpu_addr{cpu_addr_}, words{size_bytes} {} + + explicit WordManager() = default; + + void SetCpuAddress(VAddr new_cpu_addr) { + cpu_addr = new_cpu_addr; + } + + VAddr GetCpuAddr() const { + return cpu_addr; + } + + static u64 ExtractBits(u64 word, size_t page_start, size_t page_end) { + constexpr size_t number_bits = sizeof(u64) * 8; + const size_t limit_page_end = number_bits - std::min(page_end, number_bits); + u64 bits = (word >> page_start) << page_start; + bits = (bits << limit_page_end) >> limit_page_end; + return bits; + } + + static std::pair GetWordPage(VAddr address) { + const size_t converted_address = static_cast(address); + const size_t word_number = converted_address / BYTES_PER_WORD; + const size_t amount_pages = converted_address % BYTES_PER_WORD; + return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE); + } + + template + void IterateWords(size_t offset, size_t size, Func&& func) const { + using FuncReturn = std::invoke_result_t; + static constexpr bool BOOL_BREAK = std::is_same_v; + const size_t start = static_cast(std::max(static_cast(offset), 0LL)); + const size_t end = static_cast(std::max(static_cast(offset + size), 0LL)); + if (start >= SizeBytes() || end <= start) { + return; + } + auto [start_word, start_page] = GetWordPage(start); + auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL); + const size_t num_words = NumWords(); + start_word = std::min(start_word, num_words); + end_word = std::min(end_word, num_words); + const size_t diff = end_word - start_word; + end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD; + end_word = std::min(end_word, num_words); + end_page += diff * PAGES_PER_WORD; + constexpr u64 base_mask{~0ULL}; + for (size_t word_index = start_word; word_index < end_word; word_index++) { + const u64 mask = ExtractBits(base_mask, start_page, end_page); + start_page = 0; + end_page -= PAGES_PER_WORD; + if constexpr (BOOL_BREAK) { + if (func(word_index, mask)) { + return; + } + } else { + func(word_index, mask); + } + } + } + + template + void IteratePages(u64 mask, Func&& func) const { + size_t offset = 0; + while (mask != 0) { + const size_t empty_bits = std::countr_zero(mask); + offset += empty_bits; + mask = mask >> empty_bits; + + const size_t continuous_bits = std::countr_one(mask); + func(offset, continuous_bits); + mask = continuous_bits < PAGES_PER_WORD ? (mask >> continuous_bits) : 0; + offset += continuous_bits; + } + } + + /** + * Change the state of a range of pages + * + * @param dirty_addr Base address to mark or unmark as modified + * @param size Size in bytes to mark or unmark as modified + */ + template + void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) { + std::span state_words = words.template Span(); + [[maybe_unused]] std::span untracked_words = words.template Span(); + IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) { + if constexpr (type == Type::CPU) { + NotifyPageTracker(index, untracked_words[index], mask); + } + if constexpr (enable) { + state_words[index] |= mask; + if constexpr (type == Type::CPU) { + untracked_words[index] |= mask; + } + } else { + state_words[index] &= ~mask; + if constexpr (type == Type::CPU) { + untracked_words[index] &= ~mask; + } + } + }); + } + + /** + * Loop over each page in the given range, turn off those bits and notify the tracker if + * needed. Call the given function on each turned off range. + * + * @param query_cpu_range Base CPU address to loop over + * @param size Size in bytes of the CPU range to loop over + * @param func Function to call for each turned off region + */ + template + void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) { + static_assert(type != Type::Untracked); + + std::span state_words = words.template Span(); + [[maybe_unused]] std::span untracked_words = words.template Span(); + const size_t offset = query_cpu_range - cpu_addr; + bool pending = false; + size_t pending_offset{}; + size_t pending_pointer{}; + const auto release = [&]() { + func(cpu_addr + pending_offset * BYTES_PER_PAGE, + (pending_pointer - pending_offset) * BYTES_PER_PAGE); + }; + IterateWords(offset, size, [&](size_t index, u64 mask) { + if constexpr (type == Type::GPU) { + mask &= ~untracked_words[index]; + } + const u64 word = state_words[index] & mask; + if constexpr (clear) { + if constexpr (type == Type::CPU) { + NotifyPageTracker(index, untracked_words[index], mask); + } + state_words[index] &= ~mask; + if constexpr (type == Type::CPU) { + untracked_words[index] &= ~mask; + } + } + const size_t base_offset = index * PAGES_PER_WORD; + IteratePages(word, [&](size_t pages_offset, size_t pages_size) { + const auto reset = [&]() { + pending_offset = base_offset + pages_offset; + pending_pointer = base_offset + pages_offset + pages_size; + }; + if (!pending) { + reset(); + pending = true; + return; + } + if (pending_pointer == base_offset + pages_offset) { + pending_pointer += pages_size; + return; + } + release(); + reset(); + }); + }); + if (pending) { + release(); + } + } + + /** + * Returns true when a region has been modified + * + * @param offset Offset in bytes from the start of the buffer + * @param size Size in bytes of the region to query for modifications + */ + template + [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept { + static_assert(type != Type::Untracked); + + const std::span state_words = words.template Span(); + [[maybe_unused]] const std::span untracked_words = + words.template Span(); + bool result = false; + IterateWords(offset, size, [&](size_t index, u64 mask) { + if constexpr (type == Type::GPU) { + mask &= ~untracked_words[index]; + } + const u64 word = state_words[index] & mask; + if (word != 0) { + result = true; + return true; + } + return false; + }); + return result; + } + + /// Returns the number of words of the manager + [[nodiscard]] size_t NumWords() const noexcept { + return words.NumWords(); + } + + /// Returns the size in bytes of the manager + [[nodiscard]] u64 SizeBytes() const noexcept { + return words.size_bytes; + } + + /// Returns true when the buffer fits in the small vector optimization + [[nodiscard]] bool IsShort() const noexcept { + return words.IsShort(); + } + +private: + template + u64* Array() noexcept { + if constexpr (type == Type::CPU) { + return words.cpu.Pointer(IsShort()); + } else if constexpr (type == Type::GPU) { + return words.gpu.Pointer(IsShort()); + } else if constexpr (type == Type::Untracked) { + return words.untracked.Pointer(IsShort()); + } + } + + template + const u64* Array() const noexcept { + if constexpr (type == Type::CPU) { + return words.cpu.Pointer(IsShort()); + } else if constexpr (type == Type::GPU) { + return words.gpu.Pointer(IsShort()); + } else if constexpr (type == Type::Untracked) { + return words.untracked.Pointer(IsShort()); + } + } + + /** + * Notify tracker about changes in the CPU tracking state of a word in the buffer + * + * @param word_index Index to the word to notify to the tracker + * @param current_bits Current state of the word + * @param new_bits New state of the word + * + * @tparam add_to_tracker True when the tracker should start tracking the new pages + */ + template + void NotifyPageTracker(u64 word_index, u64 current_bits, u64 new_bits) const { + u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits; + VAddr addr = cpu_addr + word_index * BYTES_PER_WORD; + IteratePages(changed_bits, [&](size_t offset, size_t size) { + tracker->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE, + add_to_tracker ? 1 : -1); + }); + } + + PageManager* tracker; + VAddr cpu_addr = 0; + Words words; +}; + +} // namespace VideoCore diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index 4ef8bcdba..c2a3b53fd 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -7,6 +7,8 @@ set(SHADER_FILES detile_m32x1.comp detile_m32x2.comp detile_m32x4.comp + detile_macro32x1.comp + detile_macro32x2.comp fs_tri.vert post_process.frag ) diff --git a/src/video_core/host_shaders/detile_m32x1.comp b/src/video_core/host_shaders/detile_m32x1.comp index fecea109b..802f5f531 100644 --- a/src/video_core/host_shaders/detile_m32x1.comp +++ b/src/video_core/host_shaders/detile_m32x1.comp @@ -15,6 +15,7 @@ layout(std430, binding = 1) buffer output_buf { layout(push_constant) uniform image_info { uint num_levels; uint pitch; + uint height; uint sizes[14]; } info; diff --git a/src/video_core/host_shaders/detile_m32x2.comp b/src/video_core/host_shaders/detile_m32x2.comp index c2caa62c2..90063a185 100644 --- a/src/video_core/host_shaders/detile_m32x2.comp +++ b/src/video_core/host_shaders/detile_m32x2.comp @@ -15,6 +15,7 @@ layout(std430, binding = 1) buffer output_buf { layout(push_constant) uniform image_info { uint num_levels; uint pitch; + uint height; uint sizes[14]; } info; diff --git a/src/video_core/host_shaders/detile_m32x4.comp b/src/video_core/host_shaders/detile_m32x4.comp index 113538706..e1b988172 100644 --- a/src/video_core/host_shaders/detile_m32x4.comp +++ b/src/video_core/host_shaders/detile_m32x4.comp @@ -15,6 +15,7 @@ layout(std430, binding = 1) buffer output_buf { layout(push_constant) uniform image_info { uint num_levels; uint pitch; + uint height; uint sizes[14]; } info; diff --git a/src/video_core/host_shaders/detile_m8x1.comp b/src/video_core/host_shaders/detile_m8x1.comp index 3ca2e64bd..39d0aaeb1 100644 --- a/src/video_core/host_shaders/detile_m8x1.comp +++ b/src/video_core/host_shaders/detile_m8x1.comp @@ -18,6 +18,7 @@ layout(std430, binding = 1) buffer output_buf { layout(push_constant) uniform image_info { uint num_levels; uint pitch; + uint height; uint sizes[14]; } info; diff --git a/src/video_core/host_shaders/detile_m8x2.comp b/src/video_core/host_shaders/detile_m8x2.comp index ee9b72810..3f8e5ab33 100644 --- a/src/video_core/host_shaders/detile_m8x2.comp +++ b/src/video_core/host_shaders/detile_m8x2.comp @@ -17,6 +17,7 @@ layout(std430, binding = 1) buffer output_buf { layout(push_constant) uniform image_info { uint num_levels; uint pitch; + uint height; uint sizes[14]; } info; diff --git a/src/video_core/host_shaders/detile_macro32x1.comp b/src/video_core/host_shaders/detile_macro32x1.comp new file mode 100644 index 000000000..086fbcfb5 --- /dev/null +++ b/src/video_core/host_shaders/detile_macro32x1.comp @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 + +layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + +layout(std430, binding = 0) buffer input_buf { + uint in_data[]; +}; +layout(std430, binding = 1) buffer output_buf { + uint out_data[]; +}; + +layout(push_constant) uniform image_info { + uint num_levels; + uint pitch; + uint height; + uint c0; + uint c1; +} info; + +// Each LUT is 64 bytes, so should fit into K$ given tiled slices locality +const uint lut_32bpp[][64] = { + { + 0x00, 0x01, 0x04, 0x05, 0x40, 0x41, 0x44, 0x45, + 0x02, 0x03, 0x06, 0x07, 0x42, 0x43, 0x46, 0x47, + 0x10, 0x11, 0x14, 0x15, 0x50, 0x51, 0x54, 0x55, + 0x12, 0x13, 0x16, 0x17, 0x52, 0x53, 0x56, 0x57, + 0x80, 0x81, 0x84, 0x85, 0xc0, 0xc1, 0xc4, 0xc5, + 0x82, 0x83, 0x86, 0x87, 0xc2, 0xc3, 0xc6, 0xc7, + 0x90, 0x91, 0x94, 0x95, 0xd0, 0xd1, 0xd4, 0xd5, + 0x92, 0x93, 0x96, 0x97, 0xd2, 0xd3, 0xd6, 0xd7, + }, + { + 0x08, 0x09, 0x0c, 0x0d, 0x48, 0x49, 0x4c, 0x4d, + 0x0a, 0x0b, 0x0e, 0x0f, 0x4a, 0x4b, 0x4e, 0x4f, + 0x18, 0x19, 0x1c, 0x1d, 0x58, 0x59, 0x5c, 0x5d, + 0x1a, 0x1b, 0x1e, 0x1f, 0x5a, 0x5b, 0x5e, 0x5f, + 0x88, 0x89, 0x8c, 0x8d, 0xc8, 0xc9, 0xcc, 0xcd, + 0x8a, 0x8b, 0x8e, 0x8f, 0xca, 0xcb, 0xce, 0xcf, + 0x98, 0x99, 0x9c, 0x9d, 0xd8, 0xd9, 0xdc, 0xdd, + 0x9a, 0x9b, 0x9e, 0x9f, 0xda, 0xdb, 0xde, 0xdf, + }, + { + 0x20, 0x21, 0x24, 0x25, 0x60, 0x61, 0x64, 0x65, + 0x22, 0x23, 0x26, 0x27, 0x62, 0x63, 0x66, 0x67, + 0x30, 0x31, 0x34, 0x35, 0x70, 0x71, 0x74, 0x75, + 0x32, 0x33, 0x36, 0x37, 0x72, 0x73, 0x76, 0x77, + 0xa0, 0xa1, 0xa4, 0xa5, 0xe0, 0xe1, 0xe4, 0xe5, + 0xa2, 0xa3, 0xa6, 0xa7, 0xe2, 0xe3, 0xe6, 0xe7, + 0xb0, 0xb1, 0xb4, 0xb5, 0xf0, 0xf1, 0xf4, 0xf5, + 0xb2, 0xb3, 0xb6, 0xb7, 0xf2, 0xf3, 0xf6, 0xf7, + }, + { + 0x28, 0x29, 0x2c, 0x2d, 0x68, 0x69, 0x6c, 0x6d, + 0x2a, 0x2b, 0x2e, 0x2f, 0x6a, 0x6b, 0x6e, 0x6f, + 0x38, 0x39, 0x3c, 0x3d, 0x78, 0x79, 0x7c, 0x7d, + 0x3a, 0x3b, 0x3e, 0x3f, 0x7a, 0x7b, 0x7e, 0x7f, + 0xa8, 0xa9, 0xac, 0xad, 0xe8, 0xe9, 0xec, 0xed, + 0xaa, 0xab, 0xae, 0xaf, 0xea, 0xeb, 0xee, 0xef, + 0xb8, 0xb9, 0xbc, 0xbd, 0xf8, 0xf9, 0xfc, 0xfd, + 0xba, 0xbb, 0xbe, 0xbf, 0xfa, 0xfb, 0xfe, 0xff, + } +}; + +#define MICRO_TILE_DIM (8) +#define MICRO_TILE_SZ (1024) +#define TEXELS_PER_ELEMENT (1) +#define BPP (32) + +void main() { + uint x = gl_GlobalInvocationID.x % info.pitch; + uint y = (gl_GlobalInvocationID.x / info.pitch) % info.height; + uint z = gl_GlobalInvocationID.x / (info.pitch * info.height); + + uint col = bitfieldExtract(x, 0, 3); + uint row = bitfieldExtract(y, 0, 3); + uint lut = bitfieldExtract(z, 0, 2); + uint idx = lut_32bpp[lut][col + row * MICRO_TILE_DIM]; + + uint slice_offs = (z >> 2u) * info.c1 * MICRO_TILE_SZ; + uint tile_row = y / MICRO_TILE_DIM; + uint tile_column = x / MICRO_TILE_DIM; + uint tile_offs = ((tile_row * info.c0) + tile_column) * MICRO_TILE_SZ; + uint offs = slice_offs + tile_offs + (idx * BPP / 8); + + uint p0 = in_data[offs >> 2u]; + out_data[gl_GlobalInvocationID.x] = p0; +} diff --git a/src/video_core/host_shaders/detile_macro32x2.comp b/src/video_core/host_shaders/detile_macro32x2.comp new file mode 100644 index 000000000..296311c7a --- /dev/null +++ b/src/video_core/host_shaders/detile_macro32x2.comp @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 + +layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + +layout(std430, binding = 0) buffer input_buf { + uint in_data[]; +}; +layout(std430, binding = 1) buffer output_buf { + uint out_data[]; +}; + +layout(push_constant) uniform image_info { + uint num_levels; + uint pitch; + uint height; + uint c0; + uint c1; +} info; + +const uint lut_64bpp[][64] = { + { + 0x00, 0x01, 0x08, 0x09, 0x40, 0x41, 0x48, 0x49, + 0x02, 0x03, 0x0a, 0x0b, 0x42, 0x43, 0x4a, 0x4b, + 0x10, 0x11, 0x18, 0x19, 0x50, 0x51, 0x58, 0x59, + 0x12, 0x13, 0x1a, 0x1b, 0x52, 0x53, 0x5a, 0x5b, + 0x80, 0x81, 0x88, 0x89, 0xc0, 0xc1, 0xc8, 0xc9, + 0x82, 0x83, 0x8a, 0x8b, 0xc2, 0xc3, 0xca, 0xcb, + 0x90, 0x91, 0x98, 0x99, 0xd0, 0xd1, 0xd8, 0xd9, + 0x92, 0x93, 0x9a, 0x9b, 0xd2, 0xd3, 0xda, 0xdb, + }, + { + 0x04, 0x05, 0x0c, 0x0d, 0x44, 0x45, 0x4c, 0x4d, + 0x06, 0x07, 0x0e, 0x0f, 0x46, 0x47, 0x4e, 0x4f, + 0x14, 0x15, 0x1c, 0x1d, 0x54, 0x55, 0x5c, 0x5d, + 0x16, 0x17, 0x1e, 0x1f, 0x56, 0x57, 0x5e, 0x5f, + 0x84, 0x85, 0x8c, 0x8d, 0xc4, 0xc5, 0xcc, 0xcd, + 0x86, 0x87, 0x8e, 0x8f, 0xc6, 0xc7, 0xce, 0xcf, + 0x94, 0x95, 0x9c, 0x9d, 0xd4, 0xd5, 0xdc, 0xdd, + 0x96, 0x97, 0x9e, 0x9f, 0xd6, 0xd7, 0xde, 0xdf, + }, + { + 0x20, 0x21, 0x28, 0x29, 0x60, 0x61, 0x68, 0x69, + 0x22, 0x23, 0x2a, 0x2b, 0x62, 0x63, 0x6a, 0x6b, + 0x30, 0x31, 0x38, 0x39, 0x70, 0x71, 0x78, 0x79, + 0x32, 0x33, 0x3a, 0x3b, 0x72, 0x73, 0x7a, 0x7b, + 0xa0, 0xa1, 0xa8, 0xa9, 0xe0, 0xe1, 0xe8, 0xe9, + 0xa2, 0xa3, 0xaa, 0xab, 0xe2, 0xe3, 0xea, 0xeb, + 0xb0, 0xb1, 0xb8, 0xb9, 0xf0, 0xf1, 0xf8, 0xf9, + 0xb2, 0xb3, 0xba, 0xbb, 0xf2, 0xf3, 0xfa, 0xfb, + }, + { + 0x24, 0x25, 0x2c, 0x2d, 0x64, 0x65, 0x6c, 0x6d, + 0x26, 0x27, 0x2e, 0x2f, 0x66, 0x67, 0x6e, 0x6f, + 0x34, 0x35, 0x3c, 0x3d, 0x74, 0x75, 0x7c, 0x7d, + 0x36, 0x37, 0x3e, 0x3f, 0x76, 0x77, 0x7e, 0x7f, + 0xa4, 0xa5, 0xac, 0xad, 0xe4, 0xe5, 0xec, 0xed, + 0xa6, 0xa7, 0xae, 0xaf, 0xe6, 0xe7, 0xee, 0xef, + 0xb4, 0xb5, 0xbc, 0xbd, 0xf4, 0xf5, 0xfc, 0xfd, + 0xb6, 0xb7, 0xbe, 0xbf, 0xf6, 0xf7, 0xfe, 0xff, + }, +}; + +#define MICRO_TILE_DIM (8) +#define MICRO_TILE_SZ (2048) +#define TEXELS_PER_ELEMENT (1) +#define BPP (64) + +void main() { + uint x = gl_GlobalInvocationID.x % info.pitch; + uint y = (gl_GlobalInvocationID.x / info.pitch) % info.height; + uint z = gl_GlobalInvocationID.x / (info.pitch * info.height); + + uint col = bitfieldExtract(x, 0, 3); + uint row = bitfieldExtract(y, 0, 3); + uint lut = bitfieldExtract(z, 0, 2); + uint idx = lut_64bpp[lut][col + row * MICRO_TILE_DIM]; + + uint slice_offs = (z >> 2u) * info.c1 * MICRO_TILE_SZ; + uint tile_row = y / MICRO_TILE_DIM; + uint tile_column = x / MICRO_TILE_DIM; + uint tile_offs = ((tile_row * info.c0) + tile_column) * MICRO_TILE_SZ; + uint offs = slice_offs + tile_offs + (idx * BPP / 8); + + uint p0 = in_data[(offs >> 2) + 0]; + uint p1 = in_data[(offs >> 2) + 1]; + out_data[2 * gl_GlobalInvocationID.x + 0] = p0; + out_data[2 * gl_GlobalInvocationID.x + 1] = p1; +} diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 6df89dbae..25ff88b9d 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -116,12 +116,12 @@ vk::PrimitiveTopology PrimitiveType(AmdGpu::PrimitiveType type) { return vk::PrimitiveTopology::eTriangleStripWithAdjacency; case AmdGpu::PrimitiveType::PatchPrimitive: return vk::PrimitiveTopology::ePatchList; - case AmdGpu::PrimitiveType::QuadList: case AmdGpu::PrimitiveType::Polygon: // Needs to generate index buffer on the fly. return vk::PrimitiveTopology::eTriangleList; + case AmdGpu::PrimitiveType::QuadList: case AmdGpu::PrimitiveType::RectList: - return vk::PrimitiveTopology::eTriangleStrip; + return vk::PrimitiveTopology::ePatchList; default: UNREACHABLE(); return vk::PrimitiveTopology::eTriangleList; diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.h b/src/video_core/renderer_vulkan/liverpool_to_vk.h index 72bddc6b6..d5f8e693b 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.h +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.h @@ -70,34 +70,6 @@ vk::ClearValue ColorBufferClearValue(const AmdGpu::Liverpool::ColorBuffer& color vk::SampleCountFlagBits NumSamples(u32 num_samples, vk::SampleCountFlags supported_flags); -static constexpr u16 NumVerticesPerQuad = 4; - -inline void EmitQuadToTriangleListIndices(u8* out_ptr, u32 num_vertices) { - u16* out_data = reinterpret_cast(out_ptr); - for (u16 i = 0; i < num_vertices; i += NumVerticesPerQuad) { - *out_data++ = i; - *out_data++ = i + 1; - *out_data++ = i + 2; - *out_data++ = i; - *out_data++ = i + 2; - *out_data++ = i + 3; - } -} - -template -void ConvertQuadToTriangleListIndices(u8* out_ptr, const u8* in_ptr, u32 num_vertices) { - T* out_data = reinterpret_cast(out_ptr); - const T* in_data = reinterpret_cast(in_ptr); - for (u16 i = 0; i < num_vertices; i += NumVerticesPerQuad) { - *out_data++ = in_data[i]; - *out_data++ = in_data[i + 1]; - *out_data++ = in_data[i + 2]; - *out_data++ = in_data[i]; - *out_data++ = in_data[i + 2]; - *out_data++ = in_data[i + 3]; - } -} - inline void EmitPolygonToTriangleListIndices(u8* out_ptr, u32 num_vertices) { u16* out_data = reinterpret_cast(out_ptr); for (u16 i = 1; i < num_vertices - 1; i++) { diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index a39b18378..71659c06c 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -58,8 +58,9 @@ ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler for (const auto& image : info->images) { bindings.push_back({ .binding = binding++, - .descriptorType = image.is_storage ? vk::DescriptorType::eStorageImage - : vk::DescriptorType::eSampledImage, + .descriptorType = image.IsStorage(image.GetSharp(*info)) + ? vk::DescriptorType::eStorageImage + : vk::DescriptorType::eSampledImage, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eCompute, }); diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 222ffb5a9..ffa474a1c 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -7,25 +7,30 @@ #include #include "common/assert.h" +#include "common/io_file.h" #include "common/scope_exit.h" +#include "shader_recompiler/backend/spirv/emit_spirv_quad_rect.h" +#include "shader_recompiler/frontend/fetch_shader.h" #include "shader_recompiler/runtime_info.h" #include "video_core/amdgpu/resource.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" - -#include "shader_recompiler/frontend/fetch_shader.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/texture_cache/texture_cache.h" namespace Vulkan { -GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& scheduler_, - DescriptorHeap& desc_heap_, const GraphicsPipelineKey& key_, - vk::PipelineCache pipeline_cache, - std::span infos, - std::optional fetch_shader_, - std::span modules) +using Shader::Backend::SPIRV::AuxShaderType; + +GraphicsPipeline::GraphicsPipeline( + const Instance& instance_, Scheduler& scheduler_, DescriptorHeap& desc_heap_, + const GraphicsPipelineKey& key_, vk::PipelineCache pipeline_cache, + std::span infos, + std::span runtime_infos, + std::optional fetch_shader_, + std::span modules) : Pipeline{instance_, scheduler_, desc_heap_, pipeline_cache}, key{key_}, fetch_shader{std::move(fetch_shader_)} { const vk::Device device = instance.GetDevice(); @@ -88,11 +93,6 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul .pVertexAttributeDescriptions = vertex_attributes.data(), }; - if (key.prim_type == AmdGpu::PrimitiveType::RectList && !IsEmbeddedVs()) { - LOG_WARNING(Render_Vulkan, - "Rectangle List primitive type is only supported for embedded VS"); - } - auto prim_restart = key.enable_primitive_restart != 0; if (prim_restart && IsPrimitiveListTopology() && !instance.IsListRestartSupported()) { LOG_WARNING(Render_Vulkan, @@ -106,9 +106,11 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul ASSERT_MSG(!prim_restart || key.primitive_restart_index == 0xFFFF || key.primitive_restart_index == 0xFFFFFFFF, "Primitive restart index other than -1 is not supported yet"); - + const bool is_rect_list = key.prim_type == AmdGpu::PrimitiveType::RectList; + const bool is_quad_list = key.prim_type == AmdGpu::PrimitiveType::QuadList; + const auto& fs_info = runtime_infos[u32(Shader::LogicalStage::Fragment)].fs_info; const vk::PipelineTessellationStateCreateInfo tessellation_state = { - .patchControlPoints = key.patch_control_points, + .patchControlPoints = is_rect_list ? 3U : (is_quad_list ? 4U : key.patch_control_points), }; const vk::PipelineRasterizationStateCreateInfo raster_state = { @@ -121,7 +123,7 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul .frontFace = key.front_face == Liverpool::FrontFace::Clockwise ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise, - .depthBiasEnable = bool(key.depth_bias_enable), + .depthBiasEnable = key.depth_bias_enable, .lineWidth = 1.0f, }; @@ -131,37 +133,24 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul .sampleShadingEnable = false, }; - const vk::Viewport viewport = { - .x = 0.0f, - .y = 0.0f, - .width = 1.0f, - .height = 1.0f, - .minDepth = 0.0f, - .maxDepth = 1.0f, - }; - - const vk::Rect2D scissor = { - .offset = {0, 0}, - .extent = {1, 1}, - }; - const vk::PipelineViewportDepthClipControlCreateInfoEXT clip_control = { .negativeOneToOne = key.clip_space == Liverpool::ClipSpace::MinusWToW, }; const vk::PipelineViewportStateCreateInfo viewport_info = { .pNext = instance.IsDepthClipControlSupported() ? &clip_control : nullptr, - .viewportCount = 1, - .pViewports = &viewport, - .scissorCount = 1, - .pScissors = &scissor, }; boost::container::static_vector dynamic_states = { - vk::DynamicState::eViewport, vk::DynamicState::eScissor, - vk::DynamicState::eBlendConstants, vk::DynamicState::eDepthBounds, - vk::DynamicState::eDepthBias, vk::DynamicState::eStencilReference, - vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask, + vk::DynamicState::eViewportWithCountEXT, + vk::DynamicState::eScissorWithCountEXT, + vk::DynamicState::eBlendConstants, + vk::DynamicState::eDepthBounds, + vk::DynamicState::eDepthBias, + vk::DynamicState::eStencilReference, + vk::DynamicState::eStencilCompareMask, + vk::DynamicState::eStencilWriteMask, + vk::DynamicState::eStencilOpEXT, }; if (instance.IsColorWriteEnableSupported()) { @@ -180,31 +169,11 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul }; const vk::PipelineDepthStencilStateCreateInfo depth_info = { - .depthTestEnable = key.depth_stencil.depth_enable, - .depthWriteEnable = key.depth_stencil.depth_write_enable, - .depthCompareOp = LiverpoolToVK::CompareOp(key.depth_stencil.depth_func), - .depthBoundsTestEnable = key.depth_stencil.depth_bounds_enable, - .stencilTestEnable = key.depth_stencil.stencil_enable, - .front{ - .failOp = LiverpoolToVK::StencilOp(key.stencil.stencil_fail_front), - .passOp = LiverpoolToVK::StencilOp(key.stencil.stencil_zpass_front), - .depthFailOp = LiverpoolToVK::StencilOp(key.stencil.stencil_zfail_front), - .compareOp = LiverpoolToVK::CompareOp(key.depth_stencil.stencil_ref_func), - }, - .back{ - .failOp = LiverpoolToVK::StencilOp(key.depth_stencil.backface_enable - ? key.stencil.stencil_fail_back.Value() - : key.stencil.stencil_fail_front.Value()), - .passOp = LiverpoolToVK::StencilOp(key.depth_stencil.backface_enable - ? key.stencil.stencil_zpass_back.Value() - : key.stencil.stencil_zpass_front.Value()), - .depthFailOp = LiverpoolToVK::StencilOp(key.depth_stencil.backface_enable - ? key.stencil.stencil_zfail_back.Value() - : key.stencil.stencil_zfail_front.Value()), - .compareOp = LiverpoolToVK::CompareOp(key.depth_stencil.backface_enable - ? key.depth_stencil.stencil_bf_func.Value() - : key.depth_stencil.stencil_ref_func.Value()), - }, + .depthTestEnable = key.depth_test_enable, + .depthWriteEnable = key.depth_write_enable, + .depthCompareOp = key.depth_compare_op, + .depthBoundsTestEnable = key.depth_bounds_test_enable, + .stencilTestEnable = key.stencil_test_enable, }; boost::container::static_vector @@ -232,6 +201,14 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul .module = modules[stage], .pName = "main", }); + } else if (is_rect_list || is_quad_list) { + const auto type = is_quad_list ? AuxShaderType::QuadListTCS : AuxShaderType::RectListTCS; + auto tcs = Shader::Backend::SPIRV::EmitAuxilaryTessShader(type, fs_info); + shader_stages.emplace_back(vk::PipelineShaderStageCreateInfo{ + .stage = vk::ShaderStageFlagBits::eTessellationControl, + .module = CompileSPV(tcs, instance.GetDevice()), + .pName = "main", + }); } stage = u32(Shader::LogicalStage::TessellationEval); if (infos[stage]) { @@ -240,6 +217,14 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul .module = modules[stage], .pName = "main", }); + } else if (is_rect_list || is_quad_list) { + auto tes = + Shader::Backend::SPIRV::EmitAuxilaryTessShader(AuxShaderType::PassthroughTES, fs_info); + shader_stages.emplace_back(vk::PipelineShaderStageCreateInfo{ + .stage = vk::ShaderStageFlagBits::eTessellationEvaluation, + .module = CompileSPV(tes, instance.GetDevice()), + .pName = "main", + }); } stage = u32(Shader::LogicalStage::Fragment); if (infos[stage]) { @@ -322,8 +307,7 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul .pStages = shader_stages.data(), .pVertexInputState = !instance.IsVertexInputDynamicState() ? &vertex_input_info : nullptr, .pInputAssemblyState = &input_assembly, - .pTessellationState = - stages[u32(Shader::LogicalStage::TessellationControl)] ? &tessellation_state : nullptr, + .pTessellationState = &tessellation_state, .pViewportState = &viewport_info, .pRasterizationState = &raster_state, .pMultisampleState = &multisampling, @@ -380,8 +364,9 @@ void GraphicsPipeline::BuildDescSetLayout() { for (const auto& image : stage->images) { bindings.push_back({ .binding = binding++, - .descriptorType = image.is_storage ? vk::DescriptorType::eStorageImage - : vk::DescriptorType::eSampledImage, + .descriptorType = image.IsStorage(image.GetSharp(*stage)) + ? vk::DescriptorType::eStorageImage + : vk::DescriptorType::eSampledImage, .descriptorCount = 1, .stageFlags = gp_stage_flags, }); diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index f25341bbb..ee8afa3e6 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -18,7 +18,7 @@ class TextureCache; namespace Vulkan { -static constexpr u32 MaxShaderStages = 5; +static constexpr u32 MaxShaderStages = static_cast(Shader::LogicalStage::NumLogicalStages); static constexpr u32 MaxVertexBufferCount = 32; class Instance; @@ -36,11 +36,19 @@ struct GraphicsPipelineKey { vk::Format depth_format; vk::Format stencil_format; - Liverpool::DepthControl depth_stencil; - u32 depth_bias_enable; + struct { + bool depth_test_enable : 1; + bool depth_write_enable : 1; + bool depth_bounds_test_enable : 1; + bool depth_bias_enable : 1; + bool stencil_test_enable : 1; + // Must be named to be zero-initialized. + u8 _unused : 3; + }; + vk::CompareOp depth_compare_op; + u32 num_samples; u32 mrt_mask; - Liverpool::StencilControl stencil; AmdGpu::PrimitiveType prim_type; u32 enable_primitive_restart; u32 primitive_restart_index; @@ -64,6 +72,7 @@ public: GraphicsPipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap, const GraphicsPipelineKey& key, vk::PipelineCache pipeline_cache, std::span stages, + std::span runtime_infos, std::optional fetch_shader, std::span modules); ~GraphicsPipeline(); @@ -72,11 +81,6 @@ public: return fetch_shader; } - bool IsEmbeddedVs() const noexcept { - static constexpr size_t EmbeddedVsHash = 0x9b2da5cf47f8c29f; - return key.stage_hashes[u32(Shader::LogicalStage::Vertex)] == EmbeddedVsHash; - } - auto GetWriteMasks() const { return key.write_masks; } diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 43e02dd9d..c880cad70 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -80,8 +80,8 @@ void GatherVertexOutputs(Shader::VertexRuntimeInfo& info, : (ctl.IsCullDistEnabled(7) ? VsOutput::CullDist7 : VsOutput::None)); } -Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Stage stage, LogicalStage l_stage) { - auto info = Shader::RuntimeInfo{stage}; +const Shader::RuntimeInfo& PipelineCache::BuildRuntimeInfo(Stage stage, LogicalStage l_stage) { + auto& info = runtime_infos[u32(l_stage)]; const auto& regs = liverpool->regs; const auto BuildCommon = [&](const auto& program) { info.num_user_data = program.settings.num_user_regs; @@ -90,6 +90,7 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Stage stage, LogicalStage l_ info.fp_denorm_mode32 = program.settings.fp_denorm_mode32; info.fp_round_mode32 = program.settings.fp_round_mode32; }; + info.Initialize(stage); switch (stage) { case Stage::Local: { BuildCommon(regs.ls_program); @@ -204,7 +205,8 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, .supports_image_load_store_lod = instance_.IsImageLoadStoreLodSupported(), .needs_manual_interpolation = instance.IsFragmentShaderBarycentricSupported() && instance.GetDriverID() == vk::DriverId::eNvidiaProprietary, - .needs_lds_barriers = instance.GetDriverID() == vk::DriverId::eNvidiaProprietary, + .needs_lds_barriers = instance.GetDriverID() == vk::DriverId::eNvidiaProprietary || + instance.GetDriverID() == vk::DriverId::eMoltenvk, }; auto [cache_result, cache] = instance.GetDevice().createPipelineCacheUnique({}); ASSERT_MSG(cache_result == vk::Result::eSuccess, "Failed to create pipeline cache: {}", @@ -220,9 +222,9 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() { } const auto [it, is_new] = graphics_pipelines.try_emplace(graphics_key); if (is_new) { - it.value() = - std::make_unique(instance, scheduler, desc_heap, graphics_key, - *pipeline_cache, infos, fetch_shader, modules); + it.value() = std::make_unique(instance, scheduler, desc_heap, + graphics_key, *pipeline_cache, infos, + runtime_infos, fetch_shader, modules); if (Config::collectShadersForDebug()) { for (auto stage = 0; stage < MaxShaderStages; ++stage) { if (infos[stage]) { @@ -257,11 +259,13 @@ bool PipelineCache::RefreshGraphicsKey() { auto& regs = liverpool->regs; auto& key = graphics_key; - key.depth_stencil = regs.depth_control; - key.stencil = regs.stencil_control; - key.depth_stencil.depth_write_enable.Assign(regs.depth_control.depth_write_enable.Value() && - !regs.depth_render_control.depth_clear_enable); + key.depth_test_enable = regs.depth_control.depth_enable; + key.depth_write_enable = + regs.depth_control.depth_write_enable && !regs.depth_render_control.depth_clear_enable; + key.depth_bounds_test_enable = regs.depth_control.depth_bounds_enable; key.depth_bias_enable = regs.polygon_control.NeedsBias(); + key.depth_compare_op = LiverpoolToVK::CompareOp(regs.depth_control.depth_func); + key.stencil_test_enable = regs.depth_control.stencil_enable; const auto depth_format = instance.GetSupportedFormat( LiverpoolToVK::DepthFormat(regs.depth_buffer.z_info.format, @@ -271,13 +275,13 @@ bool PipelineCache::RefreshGraphicsKey() { key.depth_format = depth_format; } else { key.depth_format = vk::Format::eUndefined; - key.depth_stencil.depth_enable.Assign(false); + key.depth_test_enable = false; } if (regs.depth_buffer.StencilValid()) { key.stencil_format = depth_format; } else { key.stencil_format = vk::Format::eUndefined; - key.depth_stencil.stencil_enable.Assign(false); + key.stencil_test_enable = false; } key.prim_type = regs.primitive_type; @@ -493,7 +497,7 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, Shader::Runtim module = CompileSPV(spv, instance.GetDevice()); } - const auto name = fmt::format("{}_{:#018x}_{}", info.stage, info.pgm_hash, perm_idx); + const auto name = GetShaderName(info.stage, info.pgm_hash, perm_idx); Vulkan::SetObjectName(instance.GetDevice(), module, name); if (Config::collectShadersForDebug()) { DebugState.CollectShader(name, info.l_stage, module, spv, code, @@ -568,6 +572,14 @@ std::optional PipelineCache::ReplaceShader(vk::ShaderModule mo return new_module; } +std::string PipelineCache::GetShaderName(Shader::Stage stage, u64 hash, + std::optional perm) { + if (perm) { + return fmt::format("{}_{:#018x}_{}", stage, hash, *perm); + } + return fmt::format("{}_{:#018x}", stage, hash); +} + void PipelineCache::DumpShader(std::span code, u64 hash, Shader::Stage stage, size_t perm_idx, std::string_view ext) { if (!Config::dumpShaders()) { @@ -579,7 +591,7 @@ void PipelineCache::DumpShader(std::span code, u64 hash, Shader::Stag if (!std::filesystem::exists(dump_dir)) { std::filesystem::create_directories(dump_dir); } - const auto filename = fmt::format("{}_{:#018x}_{}.{}", stage, hash, perm_idx, ext); + const auto filename = fmt::format("{}.{}", GetShaderName(stage, hash, perm_idx), ext); const auto file = IOFile{dump_dir / filename, FileAccessMode::Write}; file.WriteSpan(code); } @@ -593,7 +605,7 @@ std::optional> PipelineCache::GetShaderPatch(u64 hash, Shader:: if (!std::filesystem::exists(patch_dir)) { std::filesystem::create_directories(patch_dir); } - const auto filename = fmt::format("{}_{:#018x}_{}.{}", stage, hash, perm_idx, ext); + const auto filename = fmt::format("{}.{}", GetShaderName(stage, hash, perm_idx), ext); const auto filepath = patch_dir / filename; if (!std::filesystem::exists(filepath)) { return {}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index ec4406448..b3bccd513 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -65,6 +65,9 @@ public: std::optional ReplaceShader(vk::ShaderModule module, std::span spv_code); + static std::string GetShaderName(Shader::Stage stage, u64 hash, + std::optional perm = {}); + private: bool RefreshGraphicsKey(); bool RefreshComputeKey(); @@ -76,7 +79,7 @@ private: vk::ShaderModule CompileModule(Shader::Info& info, Shader::RuntimeInfo& runtime_info, std::span code, size_t perm_idx, Shader::Backend::Bindings& binding); - Shader::RuntimeInfo BuildRuntimeInfo(Shader::Stage stage, Shader::LogicalStage l_stage); + const Shader::RuntimeInfo& BuildRuntimeInfo(Shader::Stage stage, Shader::LogicalStage l_stage); private: const Instance& instance; @@ -90,6 +93,7 @@ private: tsl::robin_map> program_cache; tsl::robin_map> compute_pipelines; tsl::robin_map> graphics_pipelines; + std::array runtime_infos{}; std::array infos{}; std::array modules{}; std::optional fetch_shader{}; diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index 139fd962d..bc55cde23 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -634,9 +634,11 @@ void Presenter::Present(Frame* frame) { swapchain.Recreate(window.GetWidth(), window.GetHeight()); } - ImGui::Core::NewFrame(); + if (!swapchain.AcquireNextImage()) { + swapchain.Recreate(window.GetWidth(), window.GetHeight()); + } - swapchain.AcquireNextImage(); + ImGui::Core::NewFrame(); const vk::Image swapchain_image = swapchain.Image(); @@ -731,7 +733,9 @@ void Presenter::Present(Frame* frame) { // Present to swapchain. std::scoped_lock submit_lock{Scheduler::submit_mutex}; - swapchain.Present(); + if (!swapchain.Present()) { + swapchain.Recreate(window.GetWidth(), window.GetHeight()); + } // Free the frame for reuse std::scoped_lock fl{free_mutex}; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index ec099b9f6..d458fa124 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -216,7 +216,7 @@ void Rasterizer::EliminateFastClear() { const auto& hint = liverpool->last_cb_extent[0]; VideoCore::TextureCache::RenderTargetDesc desc(col_buf, hint); const auto& image_view = texture_cache.FindRenderTarget(desc); - const auto& image = texture_cache.GetImage(image_view.image_id); + auto& image = texture_cache.GetImage(image_view.image_id); const vk::ImageSubresourceRange range = { .aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, @@ -225,7 +225,8 @@ void Rasterizer::EliminateFastClear() { .layerCount = col_buf.view.slice_max - col_buf.view.slice_start + 1, }; scheduler.EndRendering(); - scheduler.CommandBuffer().clearColorImage(image.image, vk::ImageLayout::eColorAttachmentOptimal, + image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); + scheduler.CommandBuffer().clearColorImage(image.image, image.last_state.layout, LiverpoolToVK::ColorBufferClearValue(col_buf).color, range); } @@ -244,7 +245,6 @@ void Rasterizer::Draw(bool is_indexed, u32 index_offset) { } auto state = PrepareRenderState(pipeline->GetMrtMask()); - if (!BindResources(pipeline)) { return; } @@ -266,10 +266,7 @@ void Rasterizer::Draw(bool is_indexed, u32 index_offset) { cmdbuf.drawIndexed(num_indices, regs.num_instances.NumInstances(), 0, s32(vertex_offset), instance_offset); } else { - const u32 num_vertices = - regs.primitive_type == AmdGpu::PrimitiveType::RectList ? 4 : regs.num_indices; - cmdbuf.draw(num_vertices, regs.num_instances.NumInstances(), vertex_offset, - instance_offset); + cmdbuf.draw(num_indices, regs.num_instances.NumInstances(), vertex_offset, instance_offset); } ResetBindings(); @@ -284,18 +281,14 @@ void Rasterizer::DrawIndirect(bool is_indexed, VAddr arg_address, u32 offset, u3 } const auto& regs = liverpool->regs; - if (regs.primitive_type == AmdGpu::PrimitiveType::QuadList || - regs.primitive_type == AmdGpu::PrimitiveType::Polygon) { - // We use a generated index buffer to convert quad lists and polygons to triangles. Since it + if (regs.primitive_type == AmdGpu::PrimitiveType::Polygon) { + // We use a generated index buffer to convert polygons to triangles. Since it // changes type of the draw, arguments are not valid for this case. We need to run a // conversion pass to repack the indirect arguments buffer first. LOG_WARNING(Render_Vulkan, "Primitive type is not supported for indirect draw"); return; } - ASSERT_MSG(regs.primitive_type != AmdGpu::PrimitiveType::RectList, - "Unsupported primitive type for indirect draw"); - const GraphicsPipeline* pipeline = pipeline_cache.GetGraphicsPipeline(); if (!pipeline) { return; @@ -662,7 +655,7 @@ void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindin if (image->binding.is_bound) { // The image is already bound. In case if it is about to be used as storage we need // to force general layout on it. - image->binding.force_general |= image_desc.is_storage; + image->binding.force_general |= image_desc.IsStorage(tsharp); } if (image->binding.is_target) { // The image is already bound as target. Since we read and output to it need to force @@ -970,7 +963,33 @@ void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) { cmdbuf.setDepthBias(regs.poly_offset.back_offset, regs.poly_offset.depth_bias, regs.poly_offset.back_scale / 16.f); } + if (regs.depth_control.stencil_enable) { + const auto front_fail_op = + LiverpoolToVK::StencilOp(regs.stencil_control.stencil_fail_front); + const auto front_pass_op = + LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zpass_front); + const auto front_depth_fail_op = + LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zfail_front); + const auto front_compare_op = LiverpoolToVK::CompareOp(regs.depth_control.stencil_ref_func); + if (regs.depth_control.backface_enable) { + const auto back_fail_op = + LiverpoolToVK::StencilOp(regs.stencil_control.stencil_fail_back); + const auto back_pass_op = + LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zpass_back); + const auto back_depth_fail_op = + LiverpoolToVK::StencilOp(regs.stencil_control.stencil_zfail_back); + const auto back_compare_op = + LiverpoolToVK::CompareOp(regs.depth_control.stencil_bf_func); + cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFront, front_fail_op, front_pass_op, + front_depth_fail_op, front_compare_op); + cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eBack, back_fail_op, back_pass_op, + back_depth_fail_op, back_compare_op); + } else { + cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFrontAndBack, front_fail_op, + front_pass_op, front_depth_fail_op, front_compare_op); + } + const auto front = regs.stencil_ref_front; const auto back = regs.stencil_ref_back; if (front.stencil_test_val == back.stencil_test_val) { @@ -980,6 +999,7 @@ void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) { cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFront, front.stencil_test_val); cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eBack, back.stencil_test_val); } + if (front.stencil_write_mask == back.stencil_write_mask) { cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack, front.stencil_write_mask); @@ -987,6 +1007,7 @@ void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) { cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFront, front.stencil_write_mask); cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eBack, back.stencil_write_mask); } + if (front.stencil_mask == back.stencil_mask) { cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack, front.stencil_mask); @@ -998,77 +1019,75 @@ void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) { } void Rasterizer::UpdateViewportScissorState() { - auto& regs = liverpool->regs; + const auto& regs = liverpool->regs; + + const auto combined_scissor_value_tl = [](s16 scr, s16 win, s16 gen, s16 win_offset) { + return std::max({scr, s16(win + win_offset), s16(gen + win_offset)}); + }; + const auto combined_scissor_value_br = [](s16 scr, s16 win, s16 gen, s16 win_offset) { + return std::min({scr, s16(win + win_offset), s16(gen + win_offset)}); + }; + const bool enable_offset = !regs.window_scissor.window_offset_disable.Value(); + + Liverpool::Scissor scsr{}; + scsr.top_left_x = combined_scissor_value_tl( + regs.screen_scissor.top_left_x, s16(regs.window_scissor.top_left_x.Value()), + s16(regs.generic_scissor.top_left_x.Value()), + enable_offset ? regs.window_offset.window_x_offset : 0); + scsr.top_left_y = combined_scissor_value_tl( + regs.screen_scissor.top_left_y, s16(regs.window_scissor.top_left_y.Value()), + s16(regs.generic_scissor.top_left_y.Value()), + enable_offset ? regs.window_offset.window_y_offset : 0); + scsr.bottom_right_x = combined_scissor_value_br( + regs.screen_scissor.bottom_right_x, regs.window_scissor.bottom_right_x, + regs.generic_scissor.bottom_right_x, + enable_offset ? regs.window_offset.window_x_offset : 0); + scsr.bottom_right_y = combined_scissor_value_br( + regs.screen_scissor.bottom_right_y, regs.window_scissor.bottom_right_y, + regs.generic_scissor.bottom_right_y, + enable_offset ? regs.window_offset.window_y_offset : 0); boost::container::static_vector viewports; boost::container::static_vector scissors; + const auto& vp_ctl = regs.viewport_control; const float reduce_z = instance.IsDepthClipControlSupported() && regs.clipper_control.clip_space == AmdGpu::Liverpool::ClipSpace::MinusWToW ? 1.0f : 0.0f; + for (u32 i = 0; i < Liverpool::NumViewports; i++) { const auto& vp = regs.viewports[i]; const auto& vp_d = regs.viewport_depths[i]; if (vp.xscale == 0) { continue; } + const auto xoffset = vp_ctl.xoffset_enable ? vp.xoffset : 0.f; + const auto xscale = vp_ctl.xscale_enable ? vp.xscale : 1.f; + const auto yoffset = vp_ctl.yoffset_enable ? vp.yoffset : 0.f; + const auto yscale = vp_ctl.yscale_enable ? vp.yscale : 1.f; + const auto zoffset = vp_ctl.zoffset_enable ? vp.zoffset : 0.f; + const auto zscale = vp_ctl.zscale_enable ? vp.zscale : 1.f; viewports.push_back({ - .x = vp.xoffset - vp.xscale, - .y = vp.yoffset - vp.yscale, - .width = vp.xscale * 2.0f, - .height = vp.yscale * 2.0f, - .minDepth = vp.zoffset - vp.zscale * reduce_z, - .maxDepth = vp.zscale + vp.zoffset, + .x = xoffset - xscale, + .y = yoffset - yscale, + .width = xscale * 2.0f, + .height = yscale * 2.0f, + .minDepth = zoffset - zscale * reduce_z, + .maxDepth = zscale + zoffset, }); - } - const bool enable_offset = !regs.window_scissor.window_offset_disable.Value(); - Liverpool::Scissor scsr{}; - const auto combined_scissor_value_tl = [](s16 scr, s16 win, s16 gen, s16 win_offset) { - return std::max({scr, s16(win + win_offset), s16(gen + win_offset)}); - }; - - scsr.top_left_x = combined_scissor_value_tl( - regs.screen_scissor.top_left_x, s16(regs.window_scissor.top_left_x.Value()), - s16(regs.generic_scissor.top_left_x.Value()), - enable_offset ? regs.window_offset.window_x_offset : 0); - - scsr.top_left_y = combined_scissor_value_tl( - regs.screen_scissor.top_left_y, s16(regs.window_scissor.top_left_y.Value()), - s16(regs.generic_scissor.top_left_y.Value()), - enable_offset ? regs.window_offset.window_y_offset : 0); - - const auto combined_scissor_value_br = [](s16 scr, s16 win, s16 gen, s16 win_offset) { - return std::min({scr, s16(win + win_offset), s16(gen + win_offset)}); - }; - - scsr.bottom_right_x = combined_scissor_value_br( - regs.screen_scissor.bottom_right_x, regs.window_scissor.bottom_right_x, - regs.generic_scissor.bottom_right_x, - enable_offset ? regs.window_offset.window_x_offset : 0); - - scsr.bottom_right_y = combined_scissor_value_br( - regs.screen_scissor.bottom_right_y, regs.window_scissor.bottom_right_y, - regs.generic_scissor.bottom_right_y, - enable_offset ? regs.window_offset.window_y_offset : 0); - - for (u32 idx = 0; idx < Liverpool::NumViewports; idx++) { - if (regs.viewports[idx].xscale == 0) { - // Scissor and viewport counts should be equal. - continue; - } auto vp_scsr = scsr; if (regs.mode_control.vport_scissor_enable) { vp_scsr.top_left_x = - std::max(vp_scsr.top_left_x, s16(regs.viewport_scissors[idx].top_left_x.Value())); + std::max(vp_scsr.top_left_x, s16(regs.viewport_scissors[i].top_left_x.Value())); vp_scsr.top_left_y = - std::max(vp_scsr.top_left_y, s16(regs.viewport_scissors[idx].top_left_y.Value())); + std::max(vp_scsr.top_left_y, s16(regs.viewport_scissors[i].top_left_y.Value())); vp_scsr.bottom_right_x = - std::min(vp_scsr.bottom_right_x, regs.viewport_scissors[idx].bottom_right_x); + std::min(vp_scsr.bottom_right_x, regs.viewport_scissors[i].bottom_right_x); vp_scsr.bottom_right_y = - std::min(vp_scsr.bottom_right_y, regs.viewport_scissors[idx].bottom_right_y); + std::min(vp_scsr.bottom_right_y, regs.viewport_scissors[i].bottom_right_y); } scissors.push_back({ .offset = {vp_scsr.top_left_x, vp_scsr.top_left_y}, @@ -1076,9 +1095,27 @@ void Rasterizer::UpdateViewportScissorState() { }); } + if (viewports.empty()) { + // Vulkan requires providing at least one viewport. + constexpr vk::Viewport empty_viewport = { + .x = 0.0f, + .y = 0.0f, + .width = 1.0f, + .height = 1.0f, + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + constexpr vk::Rect2D empty_scissor = { + .offset = {0, 0}, + .extent = {1, 1}, + }; + viewports.push_back(empty_viewport); + scissors.push_back(empty_scissor); + } + const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.setViewport(0, viewports); - cmdbuf.setScissor(0, scissors); + cmdbuf.setViewportWithCountEXT(viewports); + cmdbuf.setScissorWithCountEXT(scissors); } void Rasterizer::ScopeMarkerBegin(const std::string_view& str) { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index f6b0edda4..fd84c54ed 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -14,12 +14,16 @@ std::mutex Scheduler::submit_mutex; Scheduler::Scheduler(const Instance& instance) : instance{instance}, master_semaphore{instance}, command_pool{instance, &master_semaphore} { +#if TRACY_GPU_ENABLED profiler_scope = reinterpret_cast(std::malloc(sizeof(tracy::VkCtxScope))); +#endif AllocateWorkerCommandBuffers(); } Scheduler::~Scheduler() { +#if TRACY_GPU_ENABLED std::free(profiler_scope); +#endif } void Scheduler::BeginRendering(const RenderState& new_state) { @@ -93,23 +97,27 @@ void Scheduler::AllocateWorkerCommandBuffers() { ASSERT_MSG(begin_result == vk::Result::eSuccess, "Failed to begin command buffer: {}", vk::to_string(begin_result)); +#if TRACY_GPU_ENABLED auto* profiler_ctx = instance.GetProfilerContext(); if (profiler_ctx) { static const auto scope_loc = GPU_SCOPE_LOCATION("Guest Frame", MarkersPalette::GpuMarkerColor); new (profiler_scope) tracy::VkCtxScope{profiler_ctx, &scope_loc, current_cmdbuf, true}; } +#endif } void Scheduler::SubmitExecution(SubmitInfo& info) { std::scoped_lock lk{submit_mutex}; const u64 signal_value = master_semaphore.NextTick(); +#if TRACY_GPU_ENABLED auto* profiler_ctx = instance.GetProfilerContext(); if (profiler_ctx) { profiler_scope->~VkCtxScope(); TracyVkCollect(profiler_ctx, current_cmdbuf); } +#endif EndRendering(); auto end_result = current_cmdbuf.end(); diff --git a/src/video_core/renderer_vulkan/vk_shader_util.cpp b/src/video_core/renderer_vulkan/vk_shader_util.cpp index f9347d6e6..08703c3de 100644 --- a/src/video_core/renderer_vulkan/vk_shader_util.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_util.cpp @@ -126,6 +126,10 @@ EShLanguage ToEshShaderStage(vk::ShaderStageFlagBits stage) { return EShLanguage::EShLangVertex; case vk::ShaderStageFlagBits::eGeometry: return EShLanguage::EShLangGeometry; + case vk::ShaderStageFlagBits::eTessellationControl: + return EShLanguage::EShLangTessControl; + case vk::ShaderStageFlagBits::eTessellationEvaluation: + return EShLanguage::EShLangTessEvaluation; case vk::ShaderStageFlagBits::eFragment: return EShLanguage::EShLangFragment; case vk::ShaderStageFlagBits::eCompute: diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index d0bc7ebdc..380660a2f 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -112,7 +112,7 @@ bool Swapchain::AcquireNextImage() { return !needs_recreation; } -void Swapchain::Present() { +bool Swapchain::Present() { const vk::PresentInfoKHR present_info = { .waitSemaphoreCount = 1, @@ -131,6 +131,8 @@ void Swapchain::Present() { } frame_index = (frame_index + 1) % image_count; + + return !needs_recreation; } void Swapchain::FindPresentFormat() { diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index a41b3ca76..19ae9b2d2 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -32,7 +32,7 @@ public: bool AcquireNextImage(); /// Presents the current image and move to the next one - void Present(); + bool Present(); vk::SurfaceKHR GetSurface() const { return surface; diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 606ede558..2cc4aab38 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -366,6 +366,9 @@ void ImageInfo::UpdateSize() { mip_info.height = mip_h; break; } + case AmdGpu::TilingMode::Texture_Volume: + mip_d += (-mip_d) & 3u; + [[fallthrough]]; case AmdGpu::TilingMode::Texture_MicroTiled: { std::tie(mip_info.pitch, mip_info.size) = ImageSizeMicroTiled(mip_w, mip_h, bpp, num_samples); diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index ec1fda0d8..9e67b7f73 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -51,7 +51,7 @@ vk::ComponentSwizzle ConvertComponentSwizzle(u32 dst_sel) { } ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept - : is_storage{desc.is_storage} { + : is_storage{desc.IsStorage(image)} { const auto dfmt = image.GetDataFmt(); auto nfmt = image.GetNumberFmt(); if (is_storage && nfmt == AmdGpu::NumberFormat::Srgb) { diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 430415ed2..944f021df 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -65,7 +65,7 @@ public: struct TextureDesc : public BaseDesc { TextureDesc() = default; TextureDesc(const AmdGpu::Image& image, const Shader::ImageResource& desc) - : BaseDesc{desc.is_storage ? BindingType::Storage : BindingType::Texture, + : BaseDesc{desc.IsStorage(image) ? BindingType::Storage : BindingType::Texture, ImageInfo{image, desc}, ImageViewInfo{image, desc}} {} }; diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index 94d37c993..f6b2e2beb 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -12,6 +12,8 @@ #include "video_core/host_shaders/detile_m32x4_comp.h" #include "video_core/host_shaders/detile_m8x1_comp.h" #include "video_core/host_shaders/detile_m8x2_comp.h" +#include "video_core/host_shaders/detile_macro32x1_comp.h" +#include "video_core/host_shaders/detile_macro32x2_comp.h" #include #include @@ -19,167 +21,18 @@ namespace VideoCore { -class TileManager32 { -public: - u32 m_macro_tile_height = 0; - u32 m_bank_height = 0; - u32 m_num_banks = 0; - u32 m_num_pipes = 0; - u32 m_padded_width = 0; - u32 m_padded_height = 0; - u32 m_pipe_bits = 0; - u32 m_bank_bits = 0; - - void Init(u32 width, u32 height, bool is_neo) { - m_macro_tile_height = (is_neo ? 128 : 64); - m_bank_height = is_neo ? 2 : 1; - m_num_banks = is_neo ? 8 : 16; - m_num_pipes = is_neo ? 16 : 8; - m_padded_width = width; - if (height == 1080) { - m_padded_height = is_neo ? 1152 : 1088; - } - if (height == 720) { - m_padded_height = 768; - } - m_pipe_bits = is_neo ? 4 : 3; - m_bank_bits = is_neo ? 3 : 4; - } - - static u32 getElementIdx(u32 x, u32 y) { - u32 elem = 0; - elem |= ((x >> 0u) & 0x1u) << 0u; - elem |= ((x >> 1u) & 0x1u) << 1u; - elem |= ((y >> 0u) & 0x1u) << 2u; - elem |= ((x >> 2u) & 0x1u) << 3u; - elem |= ((y >> 1u) & 0x1u) << 4u; - elem |= ((y >> 2u) & 0x1u) << 5u; - - return elem; - } - - static u32 getPipeIdx(u32 x, u32 y, bool is_neo) { - u32 pipe = 0; - - if (!is_neo) { - pipe |= (((x >> 3u) ^ (y >> 3u) ^ (x >> 4u)) & 0x1u) << 0u; - pipe |= (((x >> 4u) ^ (y >> 4u)) & 0x1u) << 1u; - pipe |= (((x >> 5u) ^ (y >> 5u)) & 0x1u) << 2u; - } else { - pipe |= (((x >> 3u) ^ (y >> 3u) ^ (x >> 4u)) & 0x1u) << 0u; - pipe |= (((x >> 4u) ^ (y >> 4u)) & 0x1u) << 1u; - pipe |= (((x >> 5u) ^ (y >> 5u)) & 0x1u) << 2u; - pipe |= (((x >> 6u) ^ (y >> 5u)) & 0x1u) << 3u; - } - - return pipe; - } - - static u32 getBankIdx(u32 x, u32 y, u32 bank_width, u32 bank_height, u32 num_banks, - u32 num_pipes) { - const u32 x_shift_offset = std::bit_width(bank_width * num_pipes) - 1; - const u32 y_shift_offset = std::bit_width(bank_height) - 1; - const u32 xs = x >> x_shift_offset; - const u32 ys = y >> y_shift_offset; - u32 bank = 0; - switch (num_banks) { - case 8: - bank |= (((xs >> 3u) ^ (ys >> 5u)) & 0x1u) << 0u; - bank |= (((xs >> 4u) ^ (ys >> 4u) ^ (ys >> 5u)) & 0x1u) << 1u; - bank |= (((xs >> 5u) ^ (ys >> 3u)) & 0x1u) << 2u; - break; - case 16: - bank |= (((xs >> 3u) ^ (ys >> 6u)) & 0x1u) << 0u; - bank |= (((xs >> 4u) ^ (ys >> 5u) ^ (ys >> 6u)) & 0x1u) << 1u; - bank |= (((xs >> 5u) ^ (ys >> 4u)) & 0x1u) << 2u; - bank |= (((xs >> 6u) ^ (ys >> 3u)) & 0x1u) << 3u; - break; - default:; - } - - return bank; - } - - u64 getTiledOffs(u32 x, u32 y, bool is_neo) const { - u64 element_index = getElementIdx(x, y); - - u32 xh = x; - u32 yh = y; - u64 pipe = getPipeIdx(xh, yh, is_neo); - u64 bank = getBankIdx(xh, yh, 1, m_bank_height, m_num_banks, m_num_pipes); - u32 tile_bytes = (8 * 8 * 32 + 7) / 8; - u64 element_offset = (element_index * 32); - u64 tile_split_slice = 0; - - if (tile_bytes > 512) { - tile_split_slice = element_offset / (static_cast(512) * 8); - element_offset %= (static_cast(512) * 8); - tile_bytes = 512; - } - - u64 macro_tile_bytes = - (128 / 8) * (m_macro_tile_height / 8) * tile_bytes / (m_num_pipes * m_num_banks); - u64 macro_tiles_per_row = m_padded_width / 128; - u64 macro_tile_row_index = y / m_macro_tile_height; - u64 macro_tile_column_index = x / 128; - u64 macro_tile_index = - (macro_tile_row_index * macro_tiles_per_row) + macro_tile_column_index; - u64 macro_tile_offset = macro_tile_index * macro_tile_bytes; - u64 macro_tiles_per_slice = macro_tiles_per_row * (m_padded_height / m_macro_tile_height); - u64 slice_bytes = macro_tiles_per_slice * macro_tile_bytes; - u64 slice_offset = tile_split_slice * slice_bytes; - u64 tile_row_index = (y / 8) % m_bank_height; - u64 tile_index = tile_row_index; - u64 tile_offset = tile_index * tile_bytes; - - u64 tile_split_slice_rotation = ((m_num_banks / 2) + 1) * tile_split_slice; - bank ^= tile_split_slice_rotation; - bank &= (m_num_banks - 1); - - u64 total_offset = (slice_offset + macro_tile_offset + tile_offset) * 8 + element_offset; - u64 bit_offset = total_offset & 0x7u; - total_offset /= 8; - - u64 pipe_interleave_offset = total_offset & 0xffu; - u64 offset = total_offset >> 8u; - u64 byte_offset = pipe_interleave_offset | (pipe << (8u)) | (bank << (8u + m_pipe_bits)) | - (offset << (8u + m_pipe_bits + m_bank_bits)); - - return ((byte_offset << 3u) | bit_offset) / 8; - } -}; - -void ConvertTileToLinear(u8* dst, const u8* src, u32 width, u32 height, bool is_neo) { - TileManager32 t; - t.Init(width, height, is_neo); - - for (u32 y = 0; y < height; y++) { - u32 x = 0; - u64 linear_offset = y * width * 4; - - for (; x + 1 < width; x += 2) { - auto tiled_offset = t.getTiledOffs(x, y, is_neo); - - std::memcpy(dst + linear_offset, src + tiled_offset, sizeof(u64)); - linear_offset += 8; - } - if (x < width) { - auto tiled_offset = t.getTiledOffs(x, y, is_neo); - std::memcpy(dst + linear_offset, src + tiled_offset, sizeof(u32)); - } - } -} - -vk::Format DemoteImageFormatForDetiling(vk::Format format) { +static vk::Format DemoteImageFormatForDetiling(vk::Format format) { switch (format) { case vk::Format::eR8Uint: case vk::Format::eR8Unorm: return vk::Format::eR8Uint; case vk::Format::eR4G4B4A4UnormPack16: + case vk::Format::eB5G6R5UnormPack16: case vk::Format::eR5G5B5A1UnormPack16: case vk::Format::eR8G8Unorm: case vk::Format::eR16Sfloat: case vk::Format::eR16Unorm: + case vk::Format::eD16Unorm: return vk::Format::eR8G8Uint; case vk::Format::eR8G8B8A8Srgb: case vk::Format::eB8G8R8A8Srgb: @@ -233,7 +86,8 @@ vk::Format DemoteImageFormatForDetiling(vk::Format format) { const DetilerContext* TileManager::GetDetiler(const Image& image) const { const auto format = DemoteImageFormatForDetiling(image.info.pixel_format); - if (image.info.tiling_mode == AmdGpu::TilingMode::Texture_MicroTiled) { + switch (image.info.tiling_mode) { + case AmdGpu::TilingMode::Texture_MicroTiled: switch (format) { case vk::Format::eR8Uint: return &detilers[DetilerType::Micro8x1]; @@ -248,22 +102,35 @@ const DetilerContext* TileManager::GetDetiler(const Image& image) const { default: return nullptr; } + case AmdGpu::TilingMode::Texture_Volume: + switch (format) { + case vk::Format::eR32Uint: + return &detilers[DetilerType::Macro32x1]; + case vk::Format::eR32G32Uint: + return &detilers[DetilerType::Macro32x2]; + default: + return nullptr; + } + break; + default: + return nullptr; } - return nullptr; } struct DetilerParams { u32 num_levels; u32 pitch0; + u32 height; u32 sizes[14]; }; TileManager::TileManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler) : instance{instance}, scheduler{scheduler} { static const std::array detiler_shaders{ - HostShaders::DETILE_M8X1_COMP, HostShaders::DETILE_M8X2_COMP, - HostShaders::DETILE_M32X1_COMP, HostShaders::DETILE_M32X2_COMP, - HostShaders::DETILE_M32X4_COMP, + HostShaders::DETILE_M8X1_COMP, HostShaders::DETILE_M8X2_COMP, + HostShaders::DETILE_M32X1_COMP, HostShaders::DETILE_M32X2_COMP, + HostShaders::DETILE_M32X4_COMP, HostShaders::DETILE_MACRO32X1_COMP, + HostShaders::DETILE_MACRO32X2_COMP, }; boost::container::static_vector bindings{ @@ -447,14 +314,24 @@ std::pair TileManager::TryDetile(vk::Buffer in_buffer, u32 in_o set_writes); DetilerParams params; - params.pitch0 = image.info.pitch >> (image.info.props.is_block ? 2u : 0u); params.num_levels = image.info.resources.levels; + params.pitch0 = image.info.pitch >> (image.info.props.is_block ? 2u : 0u); + params.height = image.info.size.height; + if (image.info.tiling_mode == AmdGpu::TilingMode::Texture_Volume) { + ASSERT(image.info.resources.levels == 1); + ASSERT(image.info.num_bits >= 32); + const auto tiles_per_row = image.info.pitch / 8u; + const auto tiles_per_slice = tiles_per_row * ((image.info.size.height + 7u) / 8u); + params.sizes[0] = tiles_per_row; + params.sizes[1] = tiles_per_slice; + } else { - ASSERT(image.info.resources.levels <= 14); - std::memset(¶ms.sizes, 0, sizeof(params.sizes)); - for (int m = 0; m < image.info.resources.levels; ++m) { - params.sizes[m] = image.info.mips_layout[m].size * image.info.resources.layers + - (m > 0 ? params.sizes[m - 1] : 0); + ASSERT(image.info.resources.levels <= 14); + std::memset(¶ms.sizes, 0, sizeof(params.sizes)); + for (int m = 0; m < image.info.resources.levels; ++m) { + params.sizes[m] = image.info.mips_layout[m].size * image.info.resources.layers + + (m > 0 ? params.sizes[m - 1] : 0); + } } cmdbuf.pushConstants(*detiler->pl_layout, vk::ShaderStageFlagBits::eCompute, 0u, sizeof(params), diff --git a/src/video_core/texture_cache/tile_manager.h b/src/video_core/texture_cache/tile_manager.h index ed7e32c44..72860bca0 100644 --- a/src/video_core/texture_cache/tile_manager.h +++ b/src/video_core/texture_cache/tile_manager.h @@ -11,12 +11,6 @@ namespace VideoCore { class TextureCache; -/// Converts tiled texture data to linear format. -void ConvertTileToLinear(u8* dst, const u8* src, u32 width, u32 height, bool neo); - -/// Converts image format to the one used internally by detiler. -vk::Format DemoteImageFormatForDetiling(vk::Format format); - enum DetilerType : u32 { Micro8x1, Micro8x2, @@ -24,6 +18,9 @@ enum DetilerType : u32 { Micro32x2, Micro32x4, + Macro32x1, + Macro32x2, + Max };