diff --git a/CMakeLists.txt b/CMakeLists.txt index 6aa0533b6..0bc3a3c16 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 @@ -896,7 +899,8 @@ if (ENABLE_DISCORD_RPC) target_compile_definitions(shadps4 PRIVATE ENABLE_DISCORD_RPC) endif() -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") +# Optional due to https://github.com/shadps4-emu/shadPS4/issues/1704 +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND ENABLE_USERFAULTFD) target_compile_definitions(shadps4 PRIVATE ENABLE_USERFAULTFD) endif() diff --git a/documents/building-windows.md b/documents/building-windows.md index d01e7b81e..0d523692e 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. 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/config.cpp b/src/common/config.cpp index 1382d4aa7..eca9682ce 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -34,6 +34,7 @@ namespace Config { static bool isNeo = false; static bool isFullscreen = false; static bool playBGM = false; +static bool isTrophyPopupDisabled = false; static int BGMvolume = 50; static bool enableDiscordRPC = false; static u32 screenWidth = 1280; @@ -64,6 +65,8 @@ static bool vkCrashDiagnostic = false; static s16 cursorState = HideCursorState::Idle; static int cursorHideTimeout = 5; // 5 seconds (default) static bool separateupdatefolder = false; +static bool compatibilityData = false; +static bool checkCompatibilityOnStartup = false; // Gui std::vector settings_install_dirs = {}; @@ -96,6 +99,10 @@ bool isFullscreenMode() { return isFullscreen; } +bool getisTrophyPopupDisabled() { + return isTrophyPopupDisabled; +} + bool getPlayBGM() { return playBGM; } @@ -224,6 +231,14 @@ bool getSeparateUpdateEnabled() { return separateupdatefolder; } +bool getCompatibilityEnabled() { + return compatibilityData; +} + +bool getCheckCompatibilityOnStartup() { + return checkCompatibilityOnStartup; +} + void setGpuId(s32 selectedGpuId) { gpuId = selectedGpuId; } @@ -284,6 +299,10 @@ void setFullscreenMode(bool enable) { isFullscreen = enable; } +void setisTrophyPopupDisabled(bool disable) { + isTrophyPopupDisabled = disable; +} + void setPlayBGM(bool enable) { playBGM = enable; } @@ -344,6 +363,14 @@ void setSeparateUpdateEnabled(bool use) { separateupdatefolder = use; } +void setCompatibilityEnabled(bool use) { + compatibilityData = use; +} + +void setCheckCompatibilityOnStartup(bool use) { + checkCompatibilityOnStartup = use; +} + void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) { main_window_geometry_x = x; main_window_geometry_y = y; @@ -531,6 +558,7 @@ void load(const std::filesystem::path& path) { isNeo = toml::find_or(general, "isPS4Pro", false); isFullscreen = toml::find_or(general, "Fullscreen", false); playBGM = toml::find_or(general, "playBGM", false); + isTrophyPopupDisabled = toml::find_or(general, "isTrophyPopupDisabled", false); BGMvolume = toml::find_or(general, "BGMvolume", 50); enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", true); logFilter = toml::find_or(general, "logFilter", ""); @@ -544,6 +572,9 @@ void load(const std::filesystem::path& path) { isShowSplash = toml::find_or(general, "showSplash", true); isAutoUpdate = toml::find_or(general, "autoUpdate", false); separateupdatefolder = toml::find_or(general, "separateUpdateEnabled", false); + compatibilityData = toml::find_or(general, "compatibilityEnabled", false); + checkCompatibilityOnStartup = + toml::find_or(general, "checkCompatibilityOnStartup", false); } if (data.contains("Input")) { @@ -646,6 +677,7 @@ void save(const std::filesystem::path& path) { data["General"]["isPS4Pro"] = isNeo; data["General"]["Fullscreen"] = isFullscreen; + data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled; data["General"]["playBGM"] = playBGM; data["General"]["BGMvolume"] = BGMvolume; data["General"]["enableDiscordRPC"] = enableDiscordRPC; @@ -656,6 +688,8 @@ void save(const std::filesystem::path& path) { data["General"]["showSplash"] = isShowSplash; data["General"]["autoUpdate"] = isAutoUpdate; data["General"]["separateUpdateEnabled"] = separateupdatefolder; + data["General"]["compatibilityEnabled"] = compatibilityData; + data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; data["Input"]["cursorState"] = cursorState; data["Input"]["cursorHideTimeout"] = cursorHideTimeout; data["Input"]["backButtonBehavior"] = backButtonBehavior; @@ -740,6 +774,7 @@ void saveMainWindow(const std::filesystem::path& path) { void setDefaultValues() { isNeo = false; isFullscreen = false; + isTrophyPopupDisabled = false; playBGM = false; BGMvolume = 50; enableDiscordRPC = true; @@ -775,6 +810,8 @@ void setDefaultValues() { m_language = 1; gpuId = -1; separateupdatefolder = false; + compatibilityData = false; + checkCompatibilityOnStartup = false; } constexpr std::string_view GetDefaultKeyboardConfig() { diff --git a/src/common/config.h b/src/common/config.h index 3e7c746c0..56750b22d 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -19,8 +19,11 @@ bool isNeoMode(); bool isFullscreenMode(); bool getPlayBGM(); int getBGMvolume(); +bool getisTrophyPopupDisabled(); bool getEnableDiscordRPC(); bool getSeparateUpdateEnabled(); +bool getCompatibilityEnabled(); +bool getCheckCompatibilityOnStartup(); std::string getLogFilter(); std::string getLogType(); @@ -60,6 +63,7 @@ void setGpuId(s32 selectedGpuId); void setScreenWidth(u32 width); void setScreenHeight(u32 height); void setFullscreenMode(bool enable); +void setisTrophyPopupDisabled(bool disable); void setPlayBGM(bool enable); void setBGMvolume(int volume); void setEnableDiscordRPC(bool enable); @@ -69,6 +73,8 @@ void setUserName(const std::string& type); void setUpdateChannel(const std::string& type); void setSeparateUpdateEnabled(bool use); void setGameInstallDirs(const std::vector& settings_install_dirs_config); +void setCompatibilityEnabled(bool use); +void setCheckCompatibilityOnStartup(bool use); void setCursorState(s16 cursorState); void setCursorHideTimeout(int newcursorHideTimeout); 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/file_format/splash.cpp b/src/core/file_format/splash.cpp index b68702157..4eb701cf7 100644 --- a/src/core/file_format/splash.cpp +++ b/src/core/file_format/splash.cpp @@ -9,7 +9,7 @@ #include "splash.h" bool Splash::Open(const std::filesystem::path& filepath) { - ASSERT_MSG(filepath.stem().string() != "png", "Unexpected file format passed"); + ASSERT_MSG(filepath.extension().string() == ".png", "Unexpected file format passed"); Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); if (!file.IsOpen()) { diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 45ba67b93..bf340e9e3 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -171,6 +171,9 @@ void HandleTable::DeleteHandle(int d) { File* HandleTable::GetFile(int d) { std::scoped_lock lock{m_mutex}; + if (d < 0 || d >= m_files.size()) { + return nullptr; + } return m_files.at(d); } diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index 78b04cc90..db43ee928 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include +#include #include #include "common/assert.h" @@ -13,7 +15,22 @@ namespace Libraries::AudioOut { -static std::unique_ptr audio; +struct PortOut { + void* impl; + u32 samples_num; + u32 freq; + OrbisAudioOutParamFormat format; + OrbisAudioOutPort type; + int channels_num; + bool is_float; + std::array volume; + u8 sample_size; + bool is_open; +}; +std::shared_mutex ports_mutex; +std::array ports_out{}; + +static std::unique_ptr audio; static std::string_view GetAudioOutPort(OrbisAudioOutPort port) { switch (port) { @@ -70,6 +87,58 @@ static std::string_view GetAudioOutParamAttr(OrbisAudioOutParamAttr attr) { } } +static bool IsFormatFloat(const OrbisAudioOutParamFormat format) { + switch (format) { + case OrbisAudioOutParamFormat::S16Mono: + case OrbisAudioOutParamFormat::S16Stereo: + case OrbisAudioOutParamFormat::S16_8CH: + case OrbisAudioOutParamFormat::S16_8CH_Std: + return false; + case OrbisAudioOutParamFormat::FloatMono: + case OrbisAudioOutParamFormat::FloatStereo: + case OrbisAudioOutParamFormat::Float_8CH: + case OrbisAudioOutParamFormat::Float_8CH_Std: + return true; + default: + UNREACHABLE_MSG("Unknown format"); + } +} + +static int GetFormatNumChannels(const OrbisAudioOutParamFormat format) { + switch (format) { + case OrbisAudioOutParamFormat::S16Mono: + case OrbisAudioOutParamFormat::FloatMono: + return 1; + case OrbisAudioOutParamFormat::S16Stereo: + case OrbisAudioOutParamFormat::FloatStereo: + return 2; + case OrbisAudioOutParamFormat::S16_8CH: + case OrbisAudioOutParamFormat::Float_8CH: + case OrbisAudioOutParamFormat::S16_8CH_Std: + case OrbisAudioOutParamFormat::Float_8CH_Std: + return 8; + default: + UNREACHABLE_MSG("Unknown format"); + } +} + +static u8 GetFormatSampleSize(const OrbisAudioOutParamFormat format) { + switch (format) { + case OrbisAudioOutParamFormat::S16Mono: + case OrbisAudioOutParamFormat::S16Stereo: + case OrbisAudioOutParamFormat::S16_8CH: + case OrbisAudioOutParamFormat::S16_8CH_Std: + return 2; + case OrbisAudioOutParamFormat::FloatMono: + case OrbisAudioOutParamFormat::FloatStereo: + case OrbisAudioOutParamFormat::Float_8CH: + case OrbisAudioOutParamFormat::Float_8CH_Std: + return 4; + default: + UNREACHABLE_MSG("Unknown format"); + } +} + int PS4_SYSV_ABI sceAudioOutDeviceIdOpen() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; @@ -110,8 +179,21 @@ int PS4_SYSV_ABI sceAudioOutChangeAppModuleState() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutClose() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); +int PS4_SYSV_ABI sceAudioOutClose(s32 handle) { + LOG_INFO(Lib_AudioOut, "handle = {}", handle); + if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + } + + std::scoped_lock lock(ports_mutex); + auto& port = ports_out.at(handle - 1); + if (!port.is_open) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + } + + audio->Close(port.impl); + port.impl = nullptr; + port.is_open = false; return ORBIS_OK; } @@ -180,16 +262,21 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } - const auto [type, channels_num] = audio->GetStatus(handle); + std::scoped_lock lock(ports_mutex); + const auto& port = ports_out.at(handle - 1); + if (!port.is_open) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + } + state->rerouteCounter = 0; state->volume = 127; - switch (type) { + switch (port.type) { case OrbisAudioOutPort::Main: case OrbisAudioOutPort::Bgm: case OrbisAudioOutPort::Voice: state->output = 1; - state->channel = (channels_num > 2 ? 2 : channels_num); + state->channel = port.channels_num > 2 ? 2 : port.channels_num; break; case OrbisAudioOutPort::Personal: case OrbisAudioOutPort::Padspk: @@ -276,7 +363,7 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, u32 sample_rate, OrbisAudioOutParamExtendedInformation param_type) { LOG_INFO(Lib_AudioOut, - "AudioOutOpen id = {} port_type = {} index = {} lenght= {} sample_rate = {} " + "id = {} port_type = {} index = {} length = {} sample_rate = {} " "param_type = {} attr = {}", user_id, GetAudioOutPort(port_type), index, length, sample_rate, GetAudioOutParamFormat(param_type.data_format), @@ -310,7 +397,26 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, LOG_ERROR(Lib_AudioOut, "Invalid format attribute"); return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT; } - return audio->Open(port_type, length, sample_rate, format); + + std::scoped_lock lock{ports_mutex}; + const auto port = std::ranges::find(ports_out, false, &PortOut::is_open); + if (port == ports_out.end()) { + LOG_ERROR(Lib_AudioOut, "Audio ports are full"); + return ORBIS_AUDIO_OUT_ERROR_PORT_FULL; + } + + port->is_open = true; + port->type = port_type; + port->samples_num = length; + port->freq = sample_rate; + port->format = format; + port->is_float = IsFormatFloat(format); + port->channels_num = GetFormatNumChannels(format); + port->sample_size = GetFormatSampleSize(format); + port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB); + + port->impl = audio->Open(port->is_float, port->channels_num, port->freq); + return std::distance(ports_out.begin(), port) + 1; } int PS4_SYSV_ABI sceAudioOutOpenEx() { @@ -326,7 +432,15 @@ s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) { // Nothing to output return ORBIS_OK; } - return audio->Output(handle, ptr); + + auto& port = ports_out.at(handle - 1); + if (!port.is_open) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + } + + const size_t data_size = port.samples_num * port.sample_size * port.channels_num; + audio->Output(port.impl, ptr, data_size); + return ORBIS_OK; } int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) { @@ -431,7 +545,42 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) { if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } - return audio->SetVolume(handle, flag, vol); + + std::scoped_lock lock(ports_mutex); + auto& port = ports_out.at(handle - 1); + if (!port.is_open) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + } + + for (int i = 0; i < port.channels_num; i++, flag >>= 1u) { + auto bit = flag & 0x1u; + if (bit == 1) { + int src_index = i; + if (port.format == OrbisAudioOutParamFormat::Float_8CH_Std || + port.format == OrbisAudioOutParamFormat::S16_8CH_Std) { + switch (i) { + case 4: + src_index = 6; + break; + case 5: + src_index = 7; + break; + case 6: + src_index = 4; + break; + case 7: + src_index = 5; + break; + default: + break; + } + } + port.volume[i] = vol[src_index]; + } + } + + audio->SetVolume(port.impl, port.volume); + return ORBIS_OK; } int PS4_SYSV_ABI sceAudioOutSetVolumeDown() { diff --git a/src/core/libraries/audio/audioout.h b/src/core/libraries/audio/audioout.h index e8e718b87..c66a0e9f5 100644 --- a/src/core/libraries/audio/audioout.h +++ b/src/core/libraries/audio/audioout.h @@ -64,7 +64,7 @@ int PS4_SYSV_ABI sceAudioOutA3dExit(); int PS4_SYSV_ABI sceAudioOutA3dInit(); int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid(); int PS4_SYSV_ABI sceAudioOutChangeAppModuleState(); -int PS4_SYSV_ABI sceAudioOutClose(); +int PS4_SYSV_ABI sceAudioOutClose(s32 handle); int PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid(); int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode(); int PS4_SYSV_ABI sceAudioOutExGetSystemInfo(); diff --git a/src/core/libraries/audio/audioout_backend.h b/src/core/libraries/audio/audioout_backend.h new file mode 100644 index 000000000..238ef0201 --- /dev/null +++ b/src/core/libraries/audio/audioout_backend.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Libraries::AudioOut { + +class AudioOutBackend { +public: + AudioOutBackend() = default; + virtual ~AudioOutBackend() = default; + + virtual void* Open(bool is_float, int num_channels, u32 sample_rate) = 0; + virtual void Close(void* impl) = 0; + virtual void Output(void* impl, const void* ptr, size_t size) = 0; + virtual void SetVolume(void* impl, std::array ch_volumes) = 0; +}; + +} // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/sdl_audio.cpp b/src/core/libraries/audio/sdl_audio.cpp index 8cc823abe..ce385ad9c 100644 --- a/src/core/libraries/audio/sdl_audio.cpp +++ b/src/core/libraries/audio/sdl_audio.cpp @@ -1,141 +1,44 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include #include #include #include #include "common/assert.h" -#include "core/libraries/audio/audioout_error.h" #include "core/libraries/audio/sdl_audio.h" namespace Libraries::AudioOut { constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold -s32 SDLAudioOut::Open(OrbisAudioOutPort type, u32 samples_num, u32 freq, - OrbisAudioOutParamFormat format) { - std::scoped_lock lock{m_mutex}; - const auto port = std::ranges::find(ports_out, false, &PortOut::is_open); - if (port == ports_out.end()) { - LOG_ERROR(Lib_AudioOut, "Audio ports are full"); - return ORBIS_AUDIO_OUT_ERROR_PORT_FULL; - } - - port->is_open = true; - port->type = type; - port->samples_num = samples_num; - port->freq = freq; - port->format = format; - SDL_AudioFormat sampleFormat; - switch (format) { - case OrbisAudioOutParamFormat::S16Mono: - sampleFormat = SDL_AUDIO_S16; - port->channels_num = 1; - port->sample_size = 2; - break; - case OrbisAudioOutParamFormat::FloatMono: - sampleFormat = SDL_AUDIO_F32; - port->channels_num = 1; - port->sample_size = 4; - break; - case OrbisAudioOutParamFormat::S16Stereo: - sampleFormat = SDL_AUDIO_S16; - port->channels_num = 2; - port->sample_size = 2; - break; - case OrbisAudioOutParamFormat::FloatStereo: - sampleFormat = SDL_AUDIO_F32; - port->channels_num = 2; - port->sample_size = 4; - break; - case OrbisAudioOutParamFormat::S16_8CH: - sampleFormat = SDL_AUDIO_S16; - port->channels_num = 8; - port->sample_size = 2; - break; - case OrbisAudioOutParamFormat::Float_8CH: - sampleFormat = SDL_AUDIO_F32; - port->channels_num = 8; - port->sample_size = 4; - break; - case OrbisAudioOutParamFormat::S16_8CH_Std: - sampleFormat = SDL_AUDIO_S16; - port->channels_num = 8; - port->sample_size = 2; - break; - case OrbisAudioOutParamFormat::Float_8CH_Std: - sampleFormat = SDL_AUDIO_F32; - port->channels_num = 8; - port->sample_size = 4; - break; - default: - UNREACHABLE_MSG("Unknown format"); - } - - port->volume.fill(Libraries::AudioOut::SCE_AUDIO_OUT_VOLUME_0DB); - +void* SDLAudioOut::Open(bool is_float, int num_channels, u32 sample_rate) { SDL_AudioSpec fmt; SDL_zero(fmt); - fmt.format = sampleFormat; - fmt.channels = port->channels_num; - fmt.freq = freq; - port->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, NULL, NULL); - SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(port->stream)); - return std::distance(ports_out.begin(), port) + 1; + fmt.format = is_float ? SDL_AUDIO_F32 : SDL_AUDIO_S16; + fmt.channels = num_channels; + fmt.freq = sample_rate; + + auto* stream = + SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr); + SDL_ResumeAudioStreamDevice(stream); + return stream; } -s32 SDLAudioOut::Output(s32 handle, const void* ptr) { - auto& port = ports_out.at(handle - 1); - if (!port.is_open) { - return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; - } +void SDLAudioOut::Close(void* impl) { + SDL_DestroyAudioStream(static_cast(impl)); +} - const size_t data_size = port.samples_num * port.sample_size * port.channels_num; - bool result = SDL_PutAudioStreamData(port.stream, ptr, data_size); - while (SDL_GetAudioStreamAvailable(port.stream) > AUDIO_STREAM_BUFFER_THRESHOLD) { +void SDLAudioOut::Output(void* impl, const void* ptr, size_t size) { + auto* stream = static_cast(impl); + SDL_PutAudioStreamData(stream, ptr, size); + while (SDL_GetAudioStreamAvailable(stream) > AUDIO_STREAM_BUFFER_THRESHOLD) { SDL_Delay(0); } - return result ? ORBIS_OK : -1; } -s32 SDLAudioOut::SetVolume(s32 handle, s32 bitflag, s32* volume) { - using Libraries::AudioOut::OrbisAudioOutParamFormat; - auto& port = ports_out.at(handle - 1); - if (!port.is_open) { - return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; - } - - for (int i = 0; i < port.channels_num; i++, bitflag >>= 1u) { - auto bit = bitflag & 0x1u; - - if (bit == 1) { - int src_index = i; - if (port.format == OrbisAudioOutParamFormat::Float_8CH_Std || - port.format == OrbisAudioOutParamFormat::S16_8CH_Std) { - switch (i) { - case 4: - src_index = 6; - break; - case 5: - src_index = 7; - break; - case 6: - src_index = 4; - break; - case 7: - src_index = 5; - break; - default: - break; - } - } - port.volume[i] = volume[src_index]; - } - } - - return ORBIS_OK; +void SDLAudioOut::SetVolume(void* impl, std::array ch_volumes) { + // Not yet implemented } } // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/sdl_audio.h b/src/core/libraries/audio/sdl_audio.h index 2c34f8e29..d55f2f6e3 100644 --- a/src/core/libraries/audio/sdl_audio.h +++ b/src/core/libraries/audio/sdl_audio.h @@ -3,40 +3,16 @@ #pragma once -#include -#include -#include "core/libraries/audio/audioout.h" +#include "core/libraries/audio/audioout_backend.h" namespace Libraries::AudioOut { -class SDLAudioOut { +class SDLAudioOut final : public AudioOutBackend { public: - explicit SDLAudioOut() = default; - ~SDLAudioOut() = default; - - s32 Open(OrbisAudioOutPort type, u32 samples_num, u32 freq, OrbisAudioOutParamFormat format); - s32 Output(s32 handle, const void* ptr); - s32 SetVolume(s32 handle, s32 bitflag, s32* volume); - - constexpr std::pair GetStatus(s32 handle) const { - const auto& port = ports_out.at(handle - 1); - return std::make_pair(port.type, port.channels_num); - } - -private: - struct PortOut { - SDL_AudioStream* stream; - u32 samples_num; - u32 freq; - OrbisAudioOutParamFormat format; - OrbisAudioOutPort type; - int channels_num; - std::array volume; - u8 sample_size; - bool is_open; - }; - std::shared_mutex m_mutex; - std::array ports_out{}; + void* Open(bool is_float, int num_channels, u32 sample_rate) override; + void Close(void* impl) override; + void Output(void* impl, const void* ptr, size_t size) override; + void SetVolume(void* impl, std::array ch_volumes) override; }; } // namespace Libraries::AudioOut 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/np_trophy/trophy_ui.cpp b/src/core/libraries/np_trophy/trophy_ui.cpp index 55ef7b8de..4bb8c8240 100644 --- a/src/core/libraries/np_trophy/trophy_ui.cpp +++ b/src/core/libraries/np_trophy/trophy_ui.cpp @@ -5,6 +5,7 @@ #include #include #include "common/assert.h" +#include "common/config.h" #include "common/singleton.h" #include "imgui/imgui_std.h" #include "trophy_ui.h" @@ -82,7 +83,10 @@ void TrophyUI::Draw() { void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::string& trophyName) { std::lock_guard lock(queueMtx); - if (current_trophy_ui.has_value()) { + + if (Config::getisTrophyPopupDisabled()) { + return; + } else if (current_trophy_ui.has_value()) { TrophyInfo new_trophy; new_trophy.trophy_icon_path = trophyIconPath; new_trophy.trophy_name = trophyName; 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 c8d6bf36d..69fb3e377 100644 --- a/src/qt_gui/compatibility_info.cpp +++ b/src/qt_gui/compatibility_info.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "common/path_util.h" #include "compatibility_info.h" @@ -17,12 +18,14 @@ 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); - WaitForReply(reply); + if (!WaitForReply(reply)) + return; QProgressDialog dialog(tr("Fetching compatibility data, please wait"), tr("Cancel"), 0, 0, parent); @@ -43,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; } @@ -57,12 +61,17 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) { } future_watcher.setFuture(QtConcurrent::map(replies, WaitForReply)); - connect(&future_watcher, &QFutureWatcher::finished, [&]() { + connect(&future_watcher, &QFutureWatcher::finished, [&]() { for (int i = 0; i < remaining_pages; i++) { - if (replies[i]->error() == QNetworkReply::NoError) { - ExtractCompatibilityInfo(replies[i]->readAll()); + if (replies[i]->bytesAvailable()) { + if (replies[i]->error() == QNetworkReply::NoError) { + ExtractCompatibilityInfo(replies[i]->readAll()); + } + replies[i]->deleteLater(); + } else { + // This means the request timed out + return; } - replies[i]->deleteLater(); } QFile compatibility_file(m_compatibility_filename); @@ -83,6 +92,16 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) { dialog.reset(); }); + connect(&future_watcher, &QFutureWatcher::canceled, [&]() { + // Cleanup if user cancels pulling data + for (int i = 0; i < remaining_pages; i++) { + if (!replies[i]->bytesAvailable()) { + replies[i]->deleteLater(); + } else if (!replies[i]->isFinished()) { + replies[i]->abort(); + } + } + }); connect(&dialog, &QProgressDialog::canceled, &future_watcher, &QFutureWatcher::cancel); dialog.setRange(0, remaining_pages); connect(&future_watcher, &QFutureWatcher::progressValueChanged, &dialog, @@ -105,20 +124,34 @@ QNetworkReply* CompatibilityInfoClass::FetchPage(int page_num) { return reply; } -void CompatibilityInfoClass::WaitForReply(QNetworkReply* reply) { +bool CompatibilityInfoClass::WaitForReply(QNetworkReply* reply) { + // Returns true if reply succeeded, false if reply timed out + QTimer timer; + timer.setSingleShot(true); + QEventLoop loop; connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + timer.start(5000); loop.exec(); - return; + + if (timer.isActive()) { + timer.stop(); + return true; + } else { + disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + reply->abort(); + return false; + } }; CompatibilityEntry CompatibilityInfoClass::GetCompatibilityInfo(const std::string& serial) { QString title_id = QString::fromStdString(serial); if (m_compatibility_database.contains(title_id)) { { + QJsonObject compatibility_obj = m_compatibility_database[title_id].toObject(); for (int os_int = 0; os_int != static_cast(OSType::Last); os_int++) { QString os_string = OSTypeToString.at(static_cast(os_int)); - QJsonObject compatibility_obj = m_compatibility_database[title_id].toObject(); if (compatibility_obj.contains(os_string)) { QJsonObject compatibility_entry_obj = compatibility_obj[os_string].toObject(); CompatibilityEntry compatibility_entry{ @@ -133,7 +166,9 @@ CompatibilityEntry CompatibilityInfoClass::GetCompatibilityInfo(const std::strin } } } - return CompatibilityEntry{CompatibilityStatus::Unknown}; + + return CompatibilityEntry{CompatibilityStatus::Unknown, "", QDateTime::currentDateTime(), "", + 0}; } bool CompatibilityInfoClass::LoadCompatibilityFile() { diff --git a/src/qt_gui/compatibility_info.h b/src/qt_gui/compatibility_info.h index 2b970670a..0c47c27ff 100644 --- a/src/qt_gui/compatibility_info.h +++ b/src/qt_gui/compatibility_info.h @@ -57,6 +57,7 @@ class CompatibilityInfoClass : public QObject { public: // Please think of a better alternative inline static const std::unordered_map LabelToCompatStatus = { + {QStringLiteral("status-unknown"), CompatibilityStatus::Unknown}, {QStringLiteral("status-nothing"), CompatibilityStatus::Nothing}, {QStringLiteral("status-boots"), CompatibilityStatus::Boots}, {QStringLiteral("status-menus"), CompatibilityStatus::Menus}, @@ -83,11 +84,11 @@ 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); - static void WaitForReply(QNetworkReply* reply); + static bool WaitForReply(QNetworkReply* reply); QNetworkReply* FetchPage(int page_num); private: 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 d43c35ef4..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) { @@ -103,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); @@ -236,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..d26b860c1 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) { @@ -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 3e1c362ff..29dfb7119 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -144,7 +144,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"); @@ -189,7 +189,9 @@ void MainWindow::CreateDockWindows() { void MainWindow::LoadGameLists() { // Update compatibility database - m_compat_info->UpdateCompatibilityDatabase(this); + if (Config::getCheckCompatibilityOnStartup()) { + m_compat_info->UpdateCompatibilityDatabase(this); + } // Get game info from game folders. m_game_info->GetGameInfo(this); if (isTableList) { @@ -255,20 +257,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(); }); @@ -564,7 +572,6 @@ void MainWindow::CreateConnects() { } void MainWindow::StartGame() { - isGameRunning = true; BackgroundMusicPlayer::getInstance().stopMusic(); QString gamePath = ""; int table_mode = Config::getTableMode(); @@ -587,13 +594,12 @@ void MainWindow::StartGame() { } if (gamePath != "") { AddRecentFiles(gamePath); - Core::Emulator emulator; const auto path = Common::FS::PathFromQString(gamePath); if (!std::filesystem::exists(path)) { QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); return; } - emulator.Run(path); + StartEmulator(path); } } @@ -690,13 +696,12 @@ void MainWindow::BootGame() { QString(tr("Only one file can be selected!"))); } else { std::filesystem::path path = Common::FS::PathFromQString(fileNames[0]); - Core::Emulator emulator; if (!std::filesystem::exists(path)) { QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); return; } - emulator.Run(path); + StartEmulator(path); } } } @@ -1050,12 +1055,11 @@ void MainWindow::CreateRecentGameActions() { connect(m_recent_files_group, &QActionGroup::triggered, this, [this](QAction* action) { auto gamePath = Common::FS::PathFromQString(action->text()); AddRecentFiles(action->text()); // Update the list. - Core::Emulator emulator; if (!std::filesystem::exists(gamePath)) { QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); return; } - emulator.Run(gamePath); + StartEmulator(gamePath); }); } @@ -1103,4 +1107,23 @@ bool MainWindow::eventFilter(QObject* obj, QEvent* event) { } } return QMainWindow::eventFilter(obj, event); -} \ No newline at end of file +} + +void MainWindow::StartEmulator(std::filesystem::path path) { + if (isGameRunning) { + QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Game is already running!"))); + return; + } + isGameRunning = true; +#ifdef __APPLE__ + // SDL on macOS requires main thread. + Core::Emulator emulator; + emulator.Run(path); +#else + std::thread emulator_thread([=] { + Core::Emulator emulator; + emulator.Run(path); + }); + emulator_thread.detach(); +#endif +} diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index d3623c3d0..f4163defa 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -69,6 +69,7 @@ private: void LoadTranslation(); void PlayBackgroundMusic(); QIcon RecolorIcon(const QIcon& icon, bool isWhite); + void StartEmulator(std::filesystem::path); bool isIconBlack = false; bool isTableList = true; bool isGameRunning = false; diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 09d3674f7..f9095d8a0 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 @@ -54,7 +56,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); @@ -140,6 +144,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 @@ -194,6 +208,10 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->updaterGroupBox->installEventFilter(this); #endif ui->GUIgroupBox->installEventFilter(this); + ui->disableTrophycheckBox->installEventFilter(this); + ui->enableCompatibilityCheckBox->installEventFilter(this); + ui->checkCompatibilityOnStartupCheckBox->installEventFilter(this); + ui->updateCompatibilityButton->installEventFilter(this); // Input ui->hideCursorGroupBox->installEventFilter(this); @@ -263,6 +281,8 @@ void SettingsDialog::LoadValuesFromConfig() { ui->dumpShadersCheckBox->setChecked(toml::find_or(data, "GPU", "dumpShaders", false)); ui->nullGpuCheckBox->setChecked(toml::find_or(data, "GPU", "nullGpu", false)); ui->playBGMCheckBox->setChecked(toml::find_or(data, "General", "playBGM", false)); + ui->disableTrophycheckBox->setChecked( + toml::find_or(data, "General", "isTrophyPopupDisabled", false)); ui->BGMVolumeSlider->setValue(toml::find_or(data, "General", "BGMvolume", 50)); ui->discordRPCCheckbox->setChecked( toml::find_or(data, "General", "enableDiscordRPC", true)); @@ -282,6 +302,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)); @@ -397,6 +421,14 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { #endif } else if (elementName == "GUIgroupBox") { text = tr("GUIgroupBox"); + } 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 @@ -485,6 +517,7 @@ void SettingsDialog::UpdateSettings() { Config::setBackButtonBehavior(TouchPadIndex[ui->backButtonBehaviorComboBox->currentIndex()]); Config::setNeoMode(ui->ps4proCheckBox->isChecked()); Config::setFullscreenMode(ui->fullscreenCheckBox->isChecked()); + Config::setisTrophyPopupDisabled(ui->disableTrophycheckBox->isChecked()); Config::setPlayBGM(ui->playBGMCheckBox->isChecked()); Config::setNeoMode(ui->ps4proCheckBox->isChecked()); Config::setLogType(ui->logTypeComboBox->currentText().toStdString()); @@ -509,6 +542,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(); @@ -549,4 +584,4 @@ void SettingsDialog::ResetInstallFolders() { } Config::setGameInstallDirs(settings_install_dirs_config); } -} \ No newline at end of file +} 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 cce728f65..e6a9d5a58 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -11,7 +11,7 @@ 0 0 - 854 + 900 660 @@ -59,9 +59,9 @@ 0 - 0 - 832 - 431 + -97 + 815 + 618 @@ -306,7 +306,7 @@ - 275 + 265 0 @@ -469,6 +469,13 @@ 11 + + + + Disable Trophy Pop-ups + + + @@ -561,41 +568,94 @@ - - - - - 0 - 61 - - - - - - - - - Qt::Orientation::Horizontal + + + + + + 0 + 0 + - + - 40 - 20 + 0 + 0 - - - - - + + Game Compatibility + + + + 1 + + + 11 + + + + + Display Compatibility Data + + + + + + + Update Compatibility Database On Startup + + + + + + + 1 + + + 0 + + + + + + 0 + 0 + + + + + 197 + 28 + + + + + 16777215 + 16777215 + + + + Update Compatibility Database + + + + + + + + + + + @@ -696,7 +756,7 @@ 5 - + true diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index 3f861187e..dad4469cb 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -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..2448d4426 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -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..dd98c209b 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -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..afb8ebddf 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -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 9eccec8ea..1bbc68e61 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -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/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 3d1f291a6..badb18d2f 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -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..7b6583f45 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -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 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 رفتار دکمه برگشت:\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/fi.ts b/src/qt_gui/translations/fi.ts index 0a7f2b250..7c933c5ec 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -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..7a61dc292 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -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..bf0b26988 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -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..098c17bc4 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -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..dfbbf1cf3 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -672,11 +672,36 @@ GUI Settings Impostazioni GUI + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Riproduci musica del titolo + + + 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 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 + 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 Seriale + + + 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 @@ Impossibile creare il file di script di aggiornamento - + \ No newline at end of file diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index ad1f383fe..c5ee65ce0 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -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..e47db791a 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -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..ba5308358 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -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..8fe4cec9b 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -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..4dd8813b0 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -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..012f92164 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -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..2d40781cf 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -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..aa72049af 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -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..67a38eeba 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -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..0e89eaaca 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -672,11 +672,36 @@ GUI Settings Cilësimet e GUI + + + Disable Trophy Pop-ups + Disable Trophy Pop-ups + Play title music Luaj muzikën e titullit + + + 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 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 + 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 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 + 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 Seriku + + + 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 @@ Krijimi i skedarit skript të përditësimit dështoi - + \ No newline at end of file diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 4c77bc16a..33e8d0905 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -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..88d852474 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -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..a0a316df3 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -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..fb95ba935 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -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..77734bf26 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -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/liverpool.h b/src/video_core/amdgpu/liverpool.h index 4c74d37d0..83271a82d 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -428,6 +428,14 @@ struct Liverpool { BitField<0, 22, u32> tile_max; } depth_slice; + bool DepthValid() const { + return Address() != 0 && z_info.format != ZFormat::Invalid; + } + + bool StencilValid() const { + return Address() != 0 && stencil_info.format != StencilFormat::Invalid; + } + u32 Pitch() const { return (depth_size.pitch_tile_max + 1) << 3; } @@ -1275,6 +1283,26 @@ struct Liverpool { return nullptr; } + u32 NumSamples() const { + // It seems that the number of samples > 1 set in the AA config doesn't mean we're + // always rendering with MSAA, so we need to derive MS ratio from the CB and DB + // settings. + u32 num_samples = 1u; + if (color_control.mode != ColorControl::OperationMode::Disable) { + for (auto cb = 0u; cb < NumColorBuffers; ++cb) { + const auto& col_buf = color_buffers[cb]; + if (!col_buf) { + continue; + } + num_samples = std::max(num_samples, col_buf.NumSamples()); + } + } + if (depth_buffer.DepthValid() || depth_buffer.StencilValid()) { + num_samples = std::max(num_samples, depth_buffer.NumSamples()); + } + return num_samples; + } + void SetDefaults(); }; 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 e9fc06493..59c1e0bc3 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -1,717 +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 primitive type with CPU made index buffer. - const auto& regs = liverpool->regs; - if (regs.primitive_type == AmdGpu::PrimitiveType::QuadList && !is_indexed) { - is_indexed = true; - - // Emit indices. - const u32 index_size = 3 * regs.num_indices; - const auto [data, offset] = stream_buffer.Map(index_size); - Vulkan::LiverpoolToVK::EmitQuadToTriangleListIndices(data, regs.num_indices); - stream_buffer.Commit(); - - // Bind index buffer. - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.bindIndexBuffer(stream_buffer.Handle(), offset, vk::IndexType::eUint16); - return index_size / sizeof(u16); - } - if (!is_indexed) { - return regs.num_indices; - } - - // 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; - } - - // 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/host_shaders/post_process.frag b/src/video_core/host_shaders/post_process.frag index fcced3232..d501e9813 100644 --- a/src/video_core/host_shaders/post_process.frag +++ b/src/video_core/host_shaders/post_process.frag @@ -12,8 +12,14 @@ layout(push_constant) uniform settings { float gamma; } pp; +const float cutoff = 0.0031308, a = 1.055, b = 0.055, d = 12.92; +vec3 gamma(vec3 rgb) +{ + return mix(a * pow(rgb, vec3(1.0 / (2.4 + 1.0 - pp.gamma))) - b, d * rgb / pp.gamma, lessThan(rgb, vec3(cutoff))); +} + void main() { vec4 color_linear = texture(texSampler, uv); - color = pow(color_linear, vec4(1.0/(2.2 + 1.0 - pp.gamma))); + color = vec4(gamma(color_linear.rgb), color_linear.a); } diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index ec0bb3bb7..25ff88b9d 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -116,11 +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 ebd09f0ee..d5f8e693b 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.h +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.h @@ -70,31 +70,12 @@ 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) { +inline void EmitPolygonToTriangleListIndices(u8* out_ptr, u32 num_vertices) { u16* out_data = reinterpret_cast(out_ptr); - for (u16 i = 0; i < num_vertices; i += NumVerticesPerQuad) { + for (u16 i = 1; i < num_vertices - 1; i++) { + *out_data++ = 0; *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]; } } 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 444c8517e..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; } @@ -85,10 +89,6 @@ public: return key.mrt_mask; } - bool IsDepthEnabled() const { - return key.depth_stencil.depth_enable.Value(); - } - [[nodiscard]] bool IsPrimitiveListTopology() const { return key.prim_type == AmdGpu::PrimitiveType::PointList || key.prim_type == AmdGpu::PrimitiveType::LineList || diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 4b88bd374..74ae6b61c 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,33 +259,31 @@ bool PipelineCache::RefreshGraphicsKey() { auto& regs = liverpool->regs; auto& key = graphics_key; - key.depth_stencil = regs.depth_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& db = regs.depth_buffer; - const auto ds_format = instance.GetSupportedFormat( - LiverpoolToVK::DepthFormat(db.z_info.format, db.stencil_info.format), + const auto depth_format = instance.GetSupportedFormat( + LiverpoolToVK::DepthFormat(regs.depth_buffer.z_info.format, + regs.depth_buffer.stencil_info.format), vk::FormatFeatureFlagBits2::eDepthStencilAttachment); - if (db.z_info.format != AmdGpu::Liverpool::DepthBuffer::ZFormat::Invalid) { - key.depth_format = ds_format; + if (regs.depth_buffer.DepthValid()) { + key.depth_format = depth_format; } else { key.depth_format = vk::Format::eUndefined; + key.depth_test_enable = false; } - if (regs.depth_control.depth_enable) { - key.depth_stencil.depth_enable.Assign(key.depth_format != vk::Format::eUndefined); - } - key.stencil = regs.stencil_control; - - if (db.stencil_info.format != AmdGpu::Liverpool::DepthBuffer::StencilFormat::Invalid) { - key.stencil_format = key.depth_format; + if (regs.depth_buffer.StencilValid()) { + key.stencil_format = depth_format; } else { key.stencil_format = vk::Format::eUndefined; + key.stencil_test_enable = false; } - if (key.depth_stencil.stencil_enable) { - key.depth_stencil.stencil_enable.Assign(key.stencil_format != vk::Format::eUndefined); - } + key.prim_type = regs.primitive_type; key.enable_primitive_restart = regs.enable_primitive_restart & 1; key.primitive_restart_index = regs.primitive_restart_index; @@ -291,7 +291,7 @@ bool PipelineCache::RefreshGraphicsKey() { key.cull_mode = regs.polygon_control.CullingMode(); key.clip_space = regs.clipper_control.clip_space; key.front_face = regs.polygon_control.front_face; - key.num_samples = regs.aa_config.NumSamples(); + key.num_samples = regs.NumSamples(); const bool skip_cb_binding = regs.color_control.mode == AmdGpu::Liverpool::ColorControl::OperationMode::Disable; @@ -437,8 +437,6 @@ bool PipelineCache::RefreshGraphicsKey() { } } - u32 num_samples = 1u; - // Second pass to fill remain CB pipeline key data for (auto cb = 0u, remapped_cb = 0u; cb < Liverpool::NumColorBuffers; ++cb) { auto const& col_buf = regs.color_buffers[cb]; @@ -463,15 +461,8 @@ bool PipelineCache::RefreshGraphicsKey() { key.write_masks[remapped_cb] = vk::ColorComponentFlags{regs.color_target_mask.GetMask(cb)}; key.cb_shader_mask.SetMask(remapped_cb, regs.color_shader_mask.GetMask(cb)); ++remapped_cb; - - num_samples = std::max(num_samples, 1u << col_buf.attrib.num_samples_log2); } - // It seems that the number of samples > 1 set in the AA config doesn't mean we're always - // rendering with MSAA, so we need to derive MS ratio from the CB settings. - num_samples = std::max(num_samples, regs.depth_buffer.NumSamples()); - key.num_samples = num_samples; - return true; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index ec4406448..d4a51efdb 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -76,7 +76,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 +90,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 b7d829316..bc55cde23 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -154,7 +154,7 @@ void Presenter::CreatePostProcessPipeline() { const auto& fs_module = Vulkan::Compile(pp_shaders[1], vk::ShaderStageFlagBits::eFragment, instance.GetDevice()); ASSERT(fs_module); - Vulkan::SetObjectName(instance.GetDevice(), vs_module, "post_process.frag"); + Vulkan::SetObjectName(instance.GetDevice(), fs_module, "post_process.frag"); const std::array shaders_ci{ vk::PipelineShaderStageCreateInfo{ @@ -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 bd8906f86..d458fa124 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -52,7 +52,8 @@ bool Rasterizer::FilterDraw() { // There are several cases (e.g. FCE, FMask/HTile decompression) where we don't need to do an // actual draw hence can skip pipeline creation. if (regs.color_control.mode == Liverpool::ColorControl::OperationMode::EliminateFastClear) { - LOG_TRACE(Render_Vulkan, "FCE pass skipped"); + // Clears the render target if FCE is launched before any draws + EliminateFastClear(); return false; } if (regs.color_control.mode == Liverpool::ColorControl::OperationMode::FmaskDecompress) { @@ -87,9 +88,11 @@ RenderState Rasterizer::PrepareRenderState(u32 mrt_mask) { LOG_WARNING(Render_Vulkan, "Color buffers require gamma correction"); } + const bool skip_cb_binding = + regs.color_control.mode == AmdGpu::Liverpool::ColorControl::OperationMode::Disable; for (auto col_buf_id = 0u; col_buf_id < Liverpool::NumColorBuffers; ++col_buf_id) { const auto& col_buf = regs.color_buffers[col_buf_id]; - if (!col_buf) { + if (skip_cb_binding || !col_buf) { continue; } @@ -134,12 +137,8 @@ RenderState Rasterizer::PrepareRenderState(u32 mrt_mask) { }; } - using ZFormat = AmdGpu::Liverpool::DepthBuffer::ZFormat; - using StencilFormat = AmdGpu::Liverpool::DepthBuffer::StencilFormat; - if (regs.depth_buffer.Address() != 0 && - ((regs.depth_control.depth_enable && regs.depth_buffer.z_info.format != ZFormat::Invalid) || - (regs.depth_control.stencil_enable && - regs.depth_buffer.stencil_info.format != StencilFormat::Invalid))) { + if ((regs.depth_control.depth_enable && regs.depth_buffer.DepthValid()) || + (regs.depth_control.stencil_enable && regs.depth_buffer.StencilValid())) { const auto htile_address = regs.depth_htile_data_base.GetAddress(); const auto& hint = liverpool->last_db_extent; auto& [image_id, desc] = @@ -152,25 +151,36 @@ RenderState Rasterizer::PrepareRenderState(u32 mrt_mask) { image.binding.is_target = 1u; const auto slice = image_view.info.range.base.layer; - const bool is_clear = regs.depth_render_control.depth_clear_enable || - texture_cache.IsMetaCleared(htile_address, slice); + const bool is_depth_clear = regs.depth_render_control.depth_clear_enable || + texture_cache.IsMetaCleared(htile_address, slice); + const bool is_stencil_clear = regs.depth_render_control.stencil_clear_enable; ASSERT(desc.view_info.range.extent.layers == 1); state.width = std::min(state.width, image.info.size.width); state.height = std::min(state.height, image.info.size.height); - state.depth_attachment = { - .imageView = *image_view.image_view, - .imageLayout = vk::ImageLayout::eUndefined, - .loadOp = is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = vk::ClearValue{.depthStencil = {.depth = regs.depth_clear, - .stencil = regs.stencil_clear}}, - }; + state.has_depth = regs.depth_buffer.DepthValid(); + state.has_stencil = regs.depth_buffer.StencilValid(); + if (state.has_depth) { + state.depth_attachment = { + .imageView = *image_view.image_view, + .imageLayout = vk::ImageLayout::eUndefined, + .loadOp = + is_depth_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = vk::ClearValue{.depthStencil = {.depth = regs.depth_clear}}, + }; + } + if (state.has_stencil) { + state.stencil_attachment = { + .imageView = *image_view.image_view, + .imageLayout = vk::ImageLayout::eUndefined, + .loadOp = + is_stencil_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = vk::ClearValue{.depthStencil = {.stencil = regs.stencil_clear}}, + }; + } texture_cache.TouchMeta(htile_address, slice, false); - state.has_depth = - regs.depth_buffer.z_info.format != AmdGpu::Liverpool::DepthBuffer::ZFormat::Invalid; - state.has_stencil = regs.depth_buffer.stencil_info.format != - AmdGpu::Liverpool::DepthBuffer::StencilFormat::Invalid; } return state; @@ -192,6 +202,35 @@ RenderState Rasterizer::PrepareRenderState(u32 mrt_mask) { return {vertex_offset, instance_offset}; } +void Rasterizer::EliminateFastClear() { + auto& col_buf = liverpool->regs.color_buffers[0]; + if (!col_buf || !col_buf.info.fast_clear) { + return; + } + if (!texture_cache.IsMetaCleared(col_buf.CmaskAddress(), col_buf.view.slice_start)) { + return; + } + for (u32 slice = col_buf.view.slice_start; slice <= col_buf.view.slice_max; ++slice) { + texture_cache.TouchMeta(col_buf.CmaskAddress(), slice, false); + } + const auto& hint = liverpool->last_cb_extent[0]; + VideoCore::TextureCache::RenderTargetDesc desc(col_buf, hint); + const auto& image_view = texture_cache.FindRenderTarget(desc); + auto& image = texture_cache.GetImage(image_view.image_id); + const vk::ImageSubresourceRange range = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = col_buf.view.slice_start, + .layerCount = col_buf.view.slice_max - col_buf.view.slice_start + 1, + }; + scheduler.EndRendering(); + image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); + scheduler.CommandBuffer().clearColorImage(image.image, image.last_state.layout, + LiverpoolToVK::ColorBufferClearValue(col_buf).color, + range); +} + void Rasterizer::Draw(bool is_indexed, u32 index_offset) { RENDERER_TRACE; @@ -206,7 +245,6 @@ void Rasterizer::Draw(bool is_indexed, u32 index_offset) { } auto state = PrepareRenderState(pipeline->GetMrtMask()); - if (!BindResources(pipeline)) { return; } @@ -228,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(); @@ -246,17 +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) { - // For QuadList we use generated index buffer to convert quads 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, "QuadList primitive type is not supported for indirect draw"); + 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; @@ -623,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 @@ -777,6 +809,7 @@ void Rasterizer::BeginRendering(const GraphicsPipeline& pipeline, RenderState& s desc.view_info.range); } state.depth_attachment.imageLayout = image.last_state.layout; + state.stencil_attachment.imageLayout = image.last_state.layout; image.usage.depth_target = true; image.usage.stencil = has_stencil; } @@ -806,34 +839,60 @@ void Rasterizer::Resolve() { mrt1_range.base.layer = liverpool->regs.color_buffers[1].view.slice_start; mrt1_range.extent.layers = liverpool->regs.color_buffers[1].NumSlices() - mrt1_range.base.layer; - vk::ImageResolve region = { - .srcSubresource = - { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = 0, - .baseArrayLayer = mrt0_range.base.layer, - .layerCount = mrt0_range.extent.layers, - }, - .srcOffset = {0, 0, 0}, - .dstSubresource = - { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = 0, - .baseArrayLayer = mrt1_range.base.layer, - .layerCount = mrt1_range.extent.layers, - }, - .dstOffset = {0, 0, 0}, - .extent = {mrt1_image.info.size.width, mrt1_image.info.size.height, 1}, - }; - mrt0_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, mrt0_range); mrt1_image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, mrt1_range); - cmdbuf.resolveImage(mrt0_image.image, vk::ImageLayout::eTransferSrcOptimal, mrt1_image.image, - vk::ImageLayout::eTransferDstOptimal, region); + if (mrt0_image.info.num_samples == 1) { + // Vulkan does not allow resolve from a single sample image, so change it to a copy. + // Note that resolving a single-sampled image doesn't really make sense, but a game might do + // it. + vk::ImageCopy region = { + .srcSubresource = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = mrt0_range.base.layer, + .layerCount = mrt0_range.extent.layers, + }, + .srcOffset = {0, 0, 0}, + .dstSubresource = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = mrt1_range.base.layer, + .layerCount = mrt1_range.extent.layers, + }, + .dstOffset = {0, 0, 0}, + .extent = {mrt1_image.info.size.width, mrt1_image.info.size.height, 1}, + }; + cmdbuf.copyImage(mrt0_image.image, vk::ImageLayout::eTransferSrcOptimal, mrt1_image.image, + vk::ImageLayout::eTransferDstOptimal, region); + } else { + vk::ImageResolve region = { + .srcSubresource = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = mrt0_range.base.layer, + .layerCount = mrt0_range.extent.layers, + }, + .srcOffset = {0, 0, 0}, + .dstSubresource = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = mrt1_range.base.layer, + .layerCount = mrt1_range.extent.layers, + }, + .dstOffset = {0, 0, 0}, + .extent = {mrt1_image.info.size.width, mrt1_image.info.size.height, 1}, + }; + cmdbuf.resolveImage(mrt0_image.image, vk::ImageLayout::eTransferSrcOptimal, + mrt1_image.image, vk::ImageLayout::eTransferDstOptimal, region); + } } void Rasterizer::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) { @@ -904,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) { @@ -914,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); @@ -921,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); @@ -932,73 +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++) { 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}, @@ -1006,16 +1095,27 @@ void Rasterizer::UpdateViewportScissorState() { }); } - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.setViewport(0, viewports); - cmdbuf.setScissor(0, scissors); -} - -void Rasterizer::UpdateDepthStencilState() { - auto& depth = liverpool->regs.depth_control; + 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.setDepthBoundsTestEnable(depth.depth_bounds_enable); + cmdbuf.setViewportWithCountEXT(viewports); + cmdbuf.setScissorWithCountEXT(scissors); } void Rasterizer::ScopeMarkerBegin(const std::string_view& str) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index ec1b5e134..1bbb90b6c 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -71,10 +71,10 @@ private: RenderState PrepareRenderState(u32 mrt_mask); void BeginRendering(const GraphicsPipeline& pipeline, RenderState& state); void Resolve(); + void EliminateFastClear(); void UpdateDynamicState(const GraphicsPipeline& pipeline); void UpdateViewportScissorState(); - void UpdateDepthStencilState(); bool FilterDraw(); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 2c4e7a3c6..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) { @@ -30,7 +34,7 @@ void Scheduler::BeginRendering(const RenderState& new_state) { is_rendering = true; render_state = new_state; - const auto witdh = + const auto width = render_state.width != std::numeric_limits::max() ? render_state.width : 1; const auto height = render_state.height != std::numeric_limits::max() ? render_state.height : 1; @@ -39,7 +43,7 @@ void Scheduler::BeginRendering(const RenderState& new_state) { .renderArea = { .offset = {0, 0}, - .extent = {witdh, height}, + .extent = {width, height}, }, .layerCount = 1, .colorAttachmentCount = render_state.num_color_attachments, @@ -47,7 +51,7 @@ void Scheduler::BeginRendering(const RenderState& new_state) { ? render_state.color_attachments.data() : nullptr, .pDepthAttachment = render_state.has_depth ? &render_state.depth_attachment : nullptr, - .pStencilAttachment = render_state.has_stencil ? &render_state.depth_attachment : nullptr, + .pStencilAttachment = render_state.has_stencil ? &render_state.stencil_attachment : nullptr, }; current_cmdbuf.beginRendering(rendering_info); @@ -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_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index cdd33745a..fd5e68373 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -21,6 +21,7 @@ class Instance; struct RenderState { std::array color_attachments{}; vk::RenderingAttachmentInfo depth_attachment{}; + vk::RenderingAttachmentInfo stencil_attachment{}; u32 num_color_attachments{}; bool has_depth{}; bool has_stencil{}; 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 1445d41cd..2cc4aab38 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -266,7 +266,7 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::ColorBuffer& buffer, props.is_tiled = buffer.IsTiled(); tiling_mode = buffer.GetTilingMode(); pixel_format = LiverpoolToVK::SurfaceFormat(buffer.info.format, buffer.NumFormat()); - num_samples = 1 << buffer.attrib.num_fragments_log2; + num_samples = buffer.NumSamples(); num_bits = NumBits(buffer.info.format); type = vk::ImageType::e2D; size.width = hint.Valid() ? hint.width : buffer.Pitch(); @@ -289,7 +289,7 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slice props.is_tiled = false; pixel_format = LiverpoolToVK::DepthFormat(buffer.z_info.format, buffer.stencil_info.format); type = vk::ImageType::e2D; - num_samples = 1 << buffer.z_info.num_samples; // spec doesn't say it is a log2 + num_samples = buffer.NumSamples(); num_bits = buffer.NumBits(); size.width = hint.Valid() ? hint.width : buffer.Pitch(); size.height = hint.Valid() ? hint.height : buffer.Height(); @@ -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 };