diff --git a/Common/GraphicsContext.h b/Common/GraphicsContext.h index 4cc15d603e..c8495b04be 100644 --- a/Common/GraphicsContext.h +++ b/Common/GraphicsContext.h @@ -36,4 +36,4 @@ public: void Resize() override {} Draw::DrawContext *GetDrawContext() override { return nullptr; } -}; \ No newline at end of file +}; diff --git a/Common/KeyMap.cpp b/Common/KeyMap.cpp index 26d52ac7f9..d782b3cadd 100644 --- a/Common/KeyMap.cpp +++ b/Common/KeyMap.cpp @@ -399,7 +399,7 @@ void SetDefaultKeyMap(DefaultMaps dmap, bool replace) { UpdateNativeMenuKeys(); } -const KeyMap_IntStrPair key_names[] = { +static const KeyMap_IntStrPair key_names[] = { {NKCODE_A, "A"}, {NKCODE_B, "B"}, {NKCODE_C, "C"}, @@ -590,7 +590,7 @@ const KeyMap_IntStrPair key_names[] = { {NKCODE_RIGHTBRACE, "}"}, }; -const KeyMap_IntStrPair axis_names[] = { +static const KeyMap_IntStrPair axis_names[] = { {JOYSTICK_AXIS_X, "X Axis"}, {JOYSTICK_AXIS_Y, "Y Axis"}, {JOYSTICK_AXIS_PRESSURE, "Pressure"}, @@ -878,7 +878,7 @@ void LoadFromIni(IniFile &file) { IniFile::Section *controls = file.GetOrCreateSection("ControlMapping"); for (size_t i = 0; i < ARRAY_SIZE(psp_button_names); i++) { std::string value; - controls->Get(psp_button_names[i].name.c_str(), &value, ""); + controls->Get(psp_button_names[i].name, &value, ""); // Erase default mapping g_controllerMap.erase(psp_button_names[i].key); @@ -917,7 +917,7 @@ void SaveToIni(IniFile &file) { value += ","; } - controls->Set(psp_button_names[i].name.c_str(), value, ""); + controls->Set(psp_button_names[i].name, value, ""); } } diff --git a/Common/KeyMap.h b/Common/KeyMap.h index 369b12fc89..5305d9d424 100644 --- a/Common/KeyMap.h +++ b/Common/KeyMap.h @@ -86,7 +86,7 @@ namespace KeyMap { // Key & Button names struct KeyMap_IntStrPair { int key; - std::string name; + const char *name; }; // Use if you need to display the textual name diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index de0000c382..d2c04172b7 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -8,6 +8,7 @@ #include "base/basictypes.h" #include "VulkanContext.h" #include "GPU/Common/ShaderCommon.h" +#include "Common/StringUtils.h" #ifdef USE_CRT_DBG #undef new @@ -50,6 +51,20 @@ static const char *validationLayers[] = { */ }; +std::string VulkanVendorString(uint32_t vendorId) { + switch (vendorId) { + case VULKAN_VENDOR_INTEL: return "Intel"; + case VULKAN_VENDOR_NVIDIA: return "nVidia"; + case VULKAN_VENDOR_AMD: return "AMD"; + case VULKAN_VENDOR_ARM: return "ARM"; + case VULKAN_VENDOR_QUALCOMM: return "Qualcomm"; + case VULKAN_VENDOR_IMGTEC: return "Imagination"; + + default: + return StringFromFormat("%08x", vendorId); + } +} + VulkanContext::VulkanContext() { if (!VulkanLoad()) { init_error_ = "Failed to load Vulkan driver library"; @@ -120,6 +135,11 @@ VkResult VulkanContext::CreateInstance(const char *app_name, int app_ver, uint32 } VulkanLoadInstanceFunctions(instance_); + if (!CheckLayers(instance_layer_properties_, instance_layer_names_)) { + WLOG("CheckLayers for instance failed"); + // init_error_ = "Failed to validate instance layers"; + // return; + } uint32_t gpu_count = 1; res = vkEnumeratePhysicalDevices(instance_, &gpu_count, nullptr); @@ -141,11 +161,6 @@ VkResult VulkanContext::CreateInstance(const char *app_name, int app_ver, uint32 return res; } - if (!CheckLayers(instance_layer_properties_, instance_layer_names_)) { - WLOG("CheckLayers failed"); - // init_error_ = "Failed to validate instance layers"; - // return; - } return VK_SUCCESS; } @@ -333,14 +348,47 @@ bool VulkanContext::CheckLayers(const std::vector &layer_props, return true; } +int VulkanContext::GetBestPhysicalDevice() { + // Rules: Prefer discrete over embedded. + // Prefer nVidia over Intel. + + int maxScore = -1; + int best = -1; + + for (size_t i = 0; i < physical_devices_.size(); i++) { + int score = 0; + VkPhysicalDeviceProperties props; + vkGetPhysicalDeviceProperties(physical_devices_[i], &props); + switch (props.deviceType) { + case VK_PHYSICAL_DEVICE_TYPE_CPU: + score += 1; + break; + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + score += 20; + break; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + score += 10; + break; + } + if (props.vendorID == VULKAN_VENDOR_AMD) { + score += 5; + } else if (props.vendorID == VULKAN_VENDOR_NVIDIA) { + score += 5; + } + if (score > maxScore) { + best = (int)i; + maxScore = score; + } + } + return best; +} + void VulkanContext::ChooseDevice(int physical_device) { physical_device_ = physical_device; GetDeviceLayerProperties(); if (!CheckLayers(device_layer_properties_, device_layer_names_)) { - WLOG("CheckLayers failed (2)"); - // init_error_ = "Failed to validate device layers"; - // return; + WLOG("CheckLayers for device %d failed", physical_device); } vkGetPhysicalDeviceQueueFamilyProperties(physical_devices_[physical_device_], &queue_count, nullptr); @@ -786,7 +834,8 @@ bool VulkanContext::CreateShaderModule(const std::vector &spirv, VkSha void TransitionImageLayout2(VkCommandBuffer cmd, VkImage image, VkImageAspectFlags aspectMask, VkImageLayout oldImageLayout, VkImageLayout newImageLayout, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, - VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask) { + VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, + int numMipLevels) { VkImageMemoryBarrier image_memory_barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; image_memory_barrier.srcAccessMask = srcAccessMask; image_memory_barrier.dstAccessMask = dstAccessMask; @@ -795,8 +844,8 @@ void TransitionImageLayout2(VkCommandBuffer cmd, VkImage image, VkImageAspectFla image_memory_barrier.image = image; image_memory_barrier.subresourceRange.aspectMask = aspectMask; image_memory_barrier.subresourceRange.baseMipLevel = 0; - image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; - image_memory_barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + image_memory_barrier.subresourceRange.levelCount = numMipLevels; + image_memory_barrier.subresourceRange.layerCount = 1; // We never use more than one layer, and old Mali drivers have problems with VK_REMAINING_ARRAY_LAYERS/VK_REMAINING_MIP_LEVELS. image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; vkCmdPipelineBarrier(cmd, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier); @@ -906,11 +955,10 @@ const char *VulkanResultToString(VkResult res) { case VK_ERROR_OUT_OF_DATE_KHR: return "VK_ERROR_OUT_OF_DATE_KHR"; case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR"; case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"; + case VK_ERROR_OUT_OF_POOL_MEMORY_KHR: return "VK_ERROR_OUT_OF_POOL_MEMORY_KHR"; + case VK_ERROR_INVALID_EXTERNAL_HANDLE_KHX: return "VK_ERROR_INVALID_EXTERNAL_HANDLE_KHX"; + default: - return "Unknown"; + return "VK_ERROR_...(unknown)"; } } - -void VulkanAssertImpl(VkResult check, const char *function, const char *file, int line) { - const char *error = "(none)"; -} diff --git a/Common/Vulkan/VulkanContext.h b/Common/Vulkan/VulkanContext.h index 64b17f229e..8b1044c093 100644 --- a/Common/Vulkan/VulkanContext.h +++ b/Common/Vulkan/VulkanContext.h @@ -43,6 +43,18 @@ enum { VULKAN_FLAG_PRESENT_FIFO_RELAXED = 8, }; +enum { + VULKAN_VENDOR_NVIDIA = 0x000010de, + VULKAN_VENDOR_INTEL = 0x00008086, // Haha! + VULKAN_VENDOR_AMD = 0x00001002, + VULKAN_VENDOR_ARM = 0x000013B5, // Mali + VULKAN_VENDOR_QUALCOMM = 0x00005143, + VULKAN_VENDOR_IMGTEC = 0x00001010, // PowerVR +}; + +std::string VulkanVendorString(uint32_t vendorId); + + struct VulkanPhysicalDeviceInfo { VkFormat preferredDepthStencilFormat; }; @@ -203,8 +215,7 @@ public: VkResult CreateInstance(const char *app_name, int app_ver, uint32_t flags); - // TODO: Actually do some checks? - int GetBestPhysicalDevice() const { return 0; } + int GetBestPhysicalDevice(); void ChooseDevice(int physical_device); bool EnableDeviceExtension(const char *extension); VkResult CreateDevice(); @@ -393,12 +404,14 @@ private: void TransitionImageLayout2(VkCommandBuffer cmd, VkImage image, VkImageAspectFlags aspectMask, VkImageLayout oldImageLayout, VkImageLayout newImageLayout, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, - VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask); + VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, int mipLevels = VK_REMAINING_MIP_LEVELS); // GLSL compiler void init_glslang(); void finalize_glslang(); bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *pshader, std::vector &spirv, std::string *errorMessage = nullptr); +const char *VulkanResultToString(VkResult res); + #endif // UTIL_INIT diff --git a/Core/Config.cpp b/Core/Config.cpp index c300b3e573..ab996bf2bb 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -407,7 +407,6 @@ static bool DefaultSasThread() { static ConfigSetting cpuSettings[] = { ReportedConfigSetting("CPUCore", &g_Config.iCpuCore, &DefaultCpuCore, true, true), - ReportedConfigSetting("SeparateCPUThread", &g_Config.bSeparateCPUThread, false, true, true), ReportedConfigSetting("SeparateSASThread", &g_Config.bSeparateSASThread, &DefaultSasThread, true, true), ReportedConfigSetting("SeparateIOThread", &g_Config.bSeparateIOThread, true, true, true), ReportedConfigSetting("IOTimingMethod", &g_Config.iIOTimingMethod, IOTIMING_REALISTIC, true, true), diff --git a/Core/Config.h b/Core/Config.h index f5161b7466..713c54bad5 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -133,8 +133,6 @@ public: bool bFuncReplacements; bool bHideSlowWarnings; - // Definitely cannot be changed while game is running. - bool bSeparateCPUThread; bool bSeparateSASThread; bool bSeparateIOThread; int iIOTimingMethod; diff --git a/Core/CwCheat.cpp b/Core/CwCheat.cpp index 0e5ba0cb5c..b277414422 100644 --- a/Core/CwCheat.cpp +++ b/Core/CwCheat.cpp @@ -1,3 +1,7 @@ +#include +#include +#include +#include #include "i18n/i18n.h" #include "Common/StringUtils.h" #include "Common/ChunkFile.h" @@ -23,13 +27,191 @@ std::string activeCheatFile; static CWCheatEngine *cheatEngine; static bool cheatsEnabled; void hleCheat(u64 userdata, int cyclesLate); -void trim2(std::string& str); + +static inline std::string TrimString(const std::string &s) { + auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c) { + return std::isspace(c); + }); + auto wsback = std::find_if_not(s.rbegin(), s.rend(), [](int c){ + return std::isspace(c); + }).base(); + return wsback > wsfront ? std::string(wsfront, wsback) : std::string(); +} + +class CheatFileParser { +public: + CheatFileParser(const std::string &filename, const std::string &gameID = "") { +#if defined(_WIN32) && !defined(__MINGW32__) + file_.open(ConvertUTF8ToWString(activeCheatFile)); +#else + file_.open(activeCheatFile.c_str()); +#endif + + validGameID_ = ReplaceAll(gameID, "-", ""); + } + + bool Parse(); + + std::vector GetErrors() const { + return errors_; + } + + std::vector GetCheats() const { + return cheats_; + } + +protected: + void Flush(); + void AddError(const std::string &msg); + void ParseLine(const std::string &line); + void ParseDataLine(const std::string &line, CheatCodeFormat format); + bool ValidateGameID(const std::string &gameID); + + std::ifstream file_; + std::string validGameID_; + + int line_ = 0; + int games_ = 0; + std::vector errors_; + std::vector cheats_; + std::vector pendingLines_; + CheatCodeFormat codeFormat_ = CheatCodeFormat::UNDEFINED; + bool gameEnabled_ = true; + bool gameRiskyEnabled_ = false; + bool cheatEnabled_ = false; +}; + +bool CheatFileParser::Parse() { + for (line_ = 1; file_ && !file_.eof(); ++line_) { + std::string line; + getline(file_, line, '\n'); + line = TrimString(line); + + if (line.length() > 2 && line[0] == '_') { + ParseLine(line); + } else if (line.length() >= 2 && line[0] == '/' && line[1] == '/') { + // Comment, ignore. + } else if (line.length() >= 1 && line[0] == '#') { + // Comment, ignore. + } else if (line.length() > 0) { + errors_.push_back(StringFromFormat("Unrecognized content on line %d: expecting _", line_)); + } + } + + Flush(); + + return errors_.empty(); +} + +void CheatFileParser::Flush() { + if (!pendingLines_.empty()) { + cheats_.push_back({ codeFormat_, pendingLines_ }); + pendingLines_.clear(); + } + codeFormat_ = CheatCodeFormat::UNDEFINED; +} + +void CheatFileParser::AddError(const std::string &err) { + errors_.push_back(StringFromFormat("Error on line %d: %s", line_, err.c_str())); +} + +void CheatFileParser::ParseLine(const std::string &line) { + switch (line[1]) { + case 'S': + // Disc ID, validate (for multi-disc cheat files)? + Flush(); + ++games_; + + if (ValidateGameID(line.substr(2))) { + if (gameRiskyEnabled_) { + // We found the right one, so let's not use this risky stuff. + cheats_.clear(); + gameRiskyEnabled_ = false; + } + gameEnabled_ = true; + } else if (games_ == 1) { + // Old behavior was to ignore. + // For BC, let's allow if the game id doesn't match, but there's only one line. + gameRiskyEnabled_ = true; + gameEnabled_ = true; + } else { + if (gameRiskyEnabled_) { + // There are multiple games here, kill the risky stuff. + cheats_.clear(); + gameRiskyEnabled_ = false; + } + gameEnabled_ = false; + } + return; + + case 'G': + // Game title. + return; + + case 'C': + // Cheat name and activation status. + if (line.length() >= 3 && line[2] >= '1' && line[2] <= '9') { + cheatEnabled_ = true; + } else if (line.length() >= 3 && line[2] == '0') { + cheatEnabled_ = false; + } else { + AddError("could not parse cheat name line"); + cheatEnabled_ = false; + return; + } + + Flush(); + return; + + case 'L': + // CwCheat data line. + ParseDataLine(line.substr(2), CheatCodeFormat::CWCHEAT); + return; + + case 'M': + // TempAR data line. + ParseDataLine(line.substr(2), CheatCodeFormat::TEMPAR); + return; + + default: + AddError("unknown line type"); + return; + } +} + +void CheatFileParser::ParseDataLine(const std::string &line, CheatCodeFormat format) { + if (codeFormat_ == CheatCodeFormat::UNDEFINED) { + codeFormat_ = format; + } else if (codeFormat_ != format) { + AddError("mixed code format (cwcheat/tempar)"); + pendingLines_.clear(); + cheatEnabled_ = false; + } + + if (!cheatEnabled_ || !gameEnabled_) { + return; + } + + CheatLine cheatLine; + int len = 0; + if (sscanf(line.c_str(), "%x %x %n", &cheatLine.part1, &cheatLine.part2, &len) == 2) { + if ((size_t)len < line.length()) { + AddError("junk after line data"); + } + pendingLines_.push_back(cheatLine); + } else { + AddError("expecting two values"); + } +} + +bool CheatFileParser::ValidateGameID(const std::string &gameID) { + return validGameID_.empty() || ReplaceAll(TrimString(gameID), "-", "") == validGameID_; +} static void __CheatStop() { - if (cheatEngine != 0) { - cheatEngine->Exit(); + if (cheatEngine) { delete cheatEngine; - cheatEngine = 0; + cheatEngine = nullptr; } cheatsEnabled = false; } @@ -44,7 +226,7 @@ static void __CheatStart() { } cheatEngine = new CWCheatEngine(); - cheatEngine->CreateCodeList(); + cheatEngine->ParseCheats(); g_Config.bReloadCheats = false; cheatsEnabled = true; } @@ -105,7 +287,7 @@ void hleCheat(u64 userdata, int cyclesLate) { return; if (g_Config.bReloadCheats) { //Checks if the "reload cheats" button has been pressed. - cheatEngine->CreateCodeList(); + cheatEngine->ParseCheats(); g_Config.bReloadCheats = false; } cheatEngine->Run(); @@ -114,33 +296,6 @@ void hleCheat(u64 userdata, int cyclesLate) { CWCheatEngine::CWCheatEngine() { } -void CWCheatEngine::Exit() { - exit2 = true; -} - -// Takes a single code line and creates a two-part vector for each code. Feeds to CreateCodeList -static inline std::vector makeCodeParts(const std::vector& CodesList) { - std::string currentcode; - std::vector finalList; - char split_char = '\n'; - char empty = ' '; - for (size_t i = 0; i < CodesList.size(); i++) { - currentcode = CodesList[i]; - for (size_t j = 0; j < currentcode.length(); j++) { - if (currentcode[j] == empty) { - currentcode[j] = '\n'; - } - } - trim2(currentcode); - std::istringstream iss(currentcode); - std::string each; - while (std::getline(iss, each, split_char)) { - finalList.push_back(each); - } - } - return finalList; -} - void CWCheatEngine::CreateCheatFile() { activeCheatFile = GetSysDirectory(DIRECTORY_CHEATS) + gameTitle + ".ini"; File::CreateFullPath(GetSysDirectory(DIRECTORY_CHEATS)); @@ -158,138 +313,37 @@ void CWCheatEngine::CreateCheatFile() { } } -void CWCheatEngine::CreateCodeList() { //Creates code list to be used in function GetNextCode - initialCodesList = GetCodesList(); - std::string currentcode, codename; - std::vector codelist; - for (size_t i = 0; i < initialCodesList.size(); i ++) { - if (initialCodesList[i].substr(0,2) == "_S") { - continue; //Line indicates Disc ID, not needed for cheats - } - if (initialCodesList[i].substr(0,2) == "_G") { - continue; //Line indicates game Title, also not needed for cheats - } - if (initialCodesList[i].substr(0,2) == "//") { - continue; //Line indicates comment, also not needed for cheats. - } - if (initialCodesList[i].substr(0,2) == "_C") { - if (initialCodesList[i].substr(2, 1) == "0") { - cheatEnabled = false; - } else { - cheatEnabled = true; - } - codename = initialCodesList[i]; - codename.erase (codename.begin(), codename.begin()+4); - codeNameList.push_back(codename); //Import names for GUI, will be implemented later. - continue; - } - if (initialCodesList[i].substr(0,2) == "_L") { - if (cheatEnabled == true) { - currentcode = initialCodesList[i]; - std::size_t comment = currentcode.find("//"); - if (comment != std::string::npos) { - currentcode.erase(currentcode.begin() + comment, currentcode.end()); - //Purge comments after code lines before adding them to codelist - } - currentcode.erase(currentcode.begin(), currentcode.begin() + 3); - codelist.push_back(currentcode); - } - continue; - } - } - parts = makeCodeParts(codelist); +void CWCheatEngine::ParseCheats() { + CheatFileParser parser(activeCheatFile, gameTitle); + + parser.Parse(); + // TODO: Report errors. + + cheats_ = parser.GetCheats(); } -std::vector CWCheatEngine::GetNextCode() { // Feeds a size-2 vector of ints to Run() which contains the address and value of one cheat. - std::string code1; - std::string code2; - std::vector splitCode; - std::vector finalCode; - std::string modifier2 = "0"; - while (true) { - // Shouldn't splitCode be cleared here? Otherwise var1 and var2 will always be set to the first two codes.. I don't get it - if (currentCode >= parts.size()) { - code1.clear(); - code2.clear(); - break; - } - code1 = parts[currentCode++]; - trim2(code1); - if (currentCode >= parts.size()) { - code1.clear(); - code2.clear(); - break; - } - code2 = parts[currentCode++]; - trim2(code2); - splitCode.push_back(code1); - splitCode.push_back(code2); - - int var1 = (int) parseHexLong(splitCode[0]); - int var2 = (int) parseHexLong(splitCode[1]); - finalCode.push_back(var1); - finalCode.push_back(var2); - if (splitCode[0].substr(0,1) == modifier2) { - break; - } - } - return finalCode; -} - -void CWCheatEngine::SkipCodes(int count) { - for (int i = 0; i < count; i ++) { - auto code = GetNextCode(); - if (code.empty()) - { - WARN_LOG(COMMON, "CWCHEAT: Tried to skip more codes than there are, the cheat is most likely wrong"); - break; - } - if (code[0] == 0) { - break; - } - } -} - -void CWCheatEngine::SkipAllCodes() { - currentCode = codes.size() - 1; -} - -int CWCheatEngine::GetAddress(int value) { //Returns static address used by ppsspp. Some games may not like this, and causes cheats to not work without offset - int address = (value + 0x08800000) & 0x3FFFFFFF; +u32 CWCheatEngine::GetAddress(u32 value) { + // Returns static address used by ppsspp. Some games may not like this, and causes cheats to not work without offset + u32 address = (value + 0x08800000) & 0x3FFFFFFF; return address; } - -inline void trim2(std::string& str) { - size_t pos = str.find_last_not_of(' '); - if(pos != std::string::npos) { - str.erase(pos + 1); - pos = str.find_first_not_of(' '); - if(pos != std::string::npos) str.erase(0, pos); - } - else str.erase(str.begin(), str.end()); -} - -std::vector CWCheatEngine::GetCodesList() { //Reads the entire cheat list from the appropriate .ini. - std::string line; - std::vector codesList; // Read from INI here +std::vector CWCheatEngine::GetCodesList() { + // Reads the entire cheat list from the appropriate .ini. + std::vector codesList; #if defined(_WIN32) && !defined(__MINGW32__) std::ifstream list(ConvertUTF8ToWString(activeCheatFile)); #else std::ifstream list(activeCheatFile.c_str()); #endif - if (!list) { - return codesList; - } - for (int i = 0; !list.eof(); i ++) { + while (list && !list.eof()) { + std::string line; getline(list, line, '\n'); - if (line.length() > 3 && (line.substr(0,1) == "_"||line.substr(0,2) == "//")){ - codesList.push_back(line); + + if (line.length() > 3 && (line.substr(0, 1) == "_" || line.substr(0, 2) == "//")) { + codesList.push_back(TrimString(line)); } } - for(size_t i = 0; i < codesList.size(); i++) { - trim2(codesList[i]); - } return codesList; } @@ -297,469 +351,671 @@ void CWCheatEngine::InvalidateICache(u32 addr, int size) { currentMIPS->InvalidateICache(addr & ~3, size); } -void CWCheatEngine::Run() { - exit2 = false; - while (!exit2) { - currentCode = 0; +enum class CheatOp { + Invalid, + Noop, - while (true) { - std::vector code = GetNextCode(); - if (code.size() < 2) { - Exit(); - break; + Write, + Add, + Subtract, + Or, + And, + Xor, + + MultiWrite, + + CopyBytesFrom, + Delay, + + Assert, + + IfEqual, + IfNotEqual, + IfLess, + IfGreater, + + IfAddrEqual, + IfAddrNotEqual, + IfAddrLess, + IfAddrGreater, + + IfPressed, + IfNotPressed, + + CwCheatPointerCommands, +}; + +struct CheatOperation { + CheatOp op; + uint32_t addr; + int sz; + uint32_t val; + + union { + struct { + uint32_t count; + uint32_t step; + uint32_t add; + } multiWrite; + struct { + uint32_t destAddr; + } copyBytesFrom; + struct { + uint32_t skip; + } ifTypes; + struct { + uint32_t skip; + uint32_t compareAddr; + } ifAddrTypes; + struct { + int offset; + int baseOffset; + int count; + int type; + } pointerCommands; + }; +}; + +CheatOperation CWCheatEngine::InterpretNextCwCheat(const CheatCode &cheat, size_t &i) { + const CheatLine &line1 = cheat.lines[i++]; + const uint32_t &arg = line1.part2; + + // Filled as needed. + u32 addr; + + int type = line1.part1 >> 28; + switch (type) { + case 0x0: // Write 8-bit data (up to 4 bytes.) + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + if (arg & 0xFFFF0000) + return { CheatOp::Write, addr, 4, arg }; + else if (arg & 0x0000FF00) + return { CheatOp::Write, addr, 2, arg }; + else + return { CheatOp::Write, addr, 1, arg }; + + case 0x1: // Write 16-bit data. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + return { CheatOp::Write, addr, 2, arg }; + + case 0x2: // Write 32-bit data. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + return { CheatOp::Write, addr, 4, arg }; + + case 0x3: // Increment/decrement data. + addr = GetAddress(arg & 0x0FFFFFFF); + switch ((line1.part1 >> 20) & 0xF) { + case 1: + return { CheatOp::Add, addr, 1, line1.part1 & 0xFF }; + case 2: + return { CheatOp::Subtract, addr, 1, line1.part1 & 0xFF }; + case 3: + return { CheatOp::Add, addr, 2, line1.part1 & 0xFFFF }; + case 4: + return { CheatOp::Subtract, addr, 2, line1.part1 & 0xFFFF }; + case 5: + if (i < cheat.lines.size()) + return { CheatOp::Add, addr, 4, cheat.lines[i++].part1 }; + return { CheatOp::Invalid }; + case 6: + if (i < cheat.lines.size()) + return { CheatOp::Subtract, addr, 4, cheat.lines[i++].part1 }; + return { CheatOp::Invalid }; + + default: + return { CheatOp::Invalid }; + } + break; + + case 0x4: // 32-bit multi-write patch data. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + if (i < cheat.lines.size()) { + const CheatLine &line2 = cheat.lines[i++]; + + CheatOperation op = { CheatOp::MultiWrite, addr, 4, line2.part1 }; + op.multiWrite.count = arg >> 16; + op.multiWrite.step = (arg & 0xFFFF) * 4; + op.multiWrite.add = line2.part2; + return op; + } + return { CheatOp::Invalid }; + + case 0x5: // Memcpy command. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + if (i < cheat.lines.size()) { + const CheatLine &line2 = cheat.lines[i++]; + + CheatOperation op = { CheatOp::CopyBytesFrom, addr, 0, arg }; + op.copyBytesFrom.destAddr = GetAddress(line2.part1 & 0x0FFFFFFF); + return op; + } + return { CheatOp::Invalid }; + + case 0x6: // Pointer commands. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + if (i < cheat.lines.size()) { + const CheatLine &line2 = cheat.lines[i++]; + int count = (line2.part1 & 0xFFFF) - 1; + + // Validate lines to process - make sure we stay inside cheat.lines. + if (i + count > cheat.lines.size()) + return { CheatOp::Invalid }; + + CheatOperation op = { CheatOp::CwCheatPointerCommands, addr, 0, arg }; + op.pointerCommands.offset = (int)line2.part2; + // TODO: Verify sign handling. Is this really supposed to sign extend? + op.pointerCommands.baseOffset = ((int)line2.part1 >> 20) * 4; + op.pointerCommands.count = count; + op.pointerCommands.type = (line2.part1 >> 16) & 0xF; + return op; + } + return { CheatOp::Invalid }; + + case 0x7: // Boolean data operations. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + switch (arg >> 16) { + case 0x0000: // 8-bit OR. + return { CheatOp::Or, addr, 1, arg & 0xFF }; + case 0x0001: // 16-bit OR. + return { CheatOp::Or, addr, 2, arg & 0xFFFF }; + case 0x0002: // 8-bit AND. + return { CheatOp::And, addr, 1, arg & 0xFF }; + case 0x0003: // 16-bit AND. + return { CheatOp::And, addr, 2, arg & 0xFFFF }; + case 0x0004: // 8-bit XOR. + return { CheatOp::Xor, addr, 1, arg & 0xFF }; + case 0x0005: // 16-bit XOR. + return { CheatOp::Xor, addr, 2, arg & 0xFFFF }; + } + return { CheatOp::Invalid }; + + case 0x8: // 8-bit or 16-bit multi-write patch data. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + if (i < cheat.lines.size()) { + const CheatLine &line2 = cheat.lines[i++]; + const bool is8Bit = (line2.part1 & 0xFFFF0000) == 0; + const uint32_t val = is8Bit ? (line2.part1 & 0xFF) : (line2.part1 & 0xFFFF); + + CheatOperation op = { CheatOp::MultiWrite, addr, is8Bit ? 1 : 2, val }; + op.multiWrite.count = arg >> 16; + op.multiWrite.step = (arg & 0xFFFF) * (is8Bit ? 1 : 2); + op.multiWrite.add = line2.part2; + return op; + } + return { CheatOp::Invalid }; + + case 0xB: // Delay command. + return { CheatOp::Delay, 0, 0, arg }; + + case 0xC: // 32-bit equal check / code stopper. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + return { CheatOp::Assert, addr, 4, arg }; + + case 0xD: // Line skip tests & joker codes. + switch (arg >> 28) { + case 0x0: // 16-bit next line skip test. + case 0x2: // 8-bit next line skip test. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + { + const bool is8Bit = (arg >> 28) == 0x2; + const uint32_t val = is8Bit ? (arg & 0xFF) : (arg & 0xFFFF); + + CheatOp opcode; + switch ((arg >> 20) & 0xF) { + case 0x0: + opcode = CheatOp::IfEqual; + break; + case 0x1: + opcode = CheatOp::IfNotEqual; + break; + case 0x2: + opcode = CheatOp::IfLess; + break; + case 0x3: + opcode = CheatOp::IfGreater; + break; + default: + return { CheatOp::Invalid }; + } + + CheatOperation op = { opcode, addr, is8Bit ? 1 : 2, val }; + op.ifTypes.skip = 1; + return op; } - int value; - unsigned int comm = code[0]; - u32 arg = code[1]; - int addr = GetAddress(comm & 0x0FFFFFFF); + case 0x1: // Joker code - button pressed. + case 0x3: // Inverse joker code - button not pressed. + { + bool pressed = (arg >> 28) == 0x1; + CheatOperation op = { pressed ? CheatOp::IfPressed : CheatOp::IfNotPressed, 0, 0, arg & 0x0FFFFFFF }; + op.ifTypes.skip = (line1.part1 & 0xFF) + 1; + return op; + } - switch (comm >> 28) { - case 0: // 8-bit write.But need more check - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - if (arg < 0x00000100) // 8-bit - Memory::Write_U8((u8) arg, addr); - else if (arg < 0x00010000) // 16-bit - Memory::Write_U16((u16) arg, addr); - else // 32-bit - Memory::Write_U32((u32) arg, addr); - } - break; - case 0x1: // 16-bit write - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - Memory::Write_U16((u16) arg, addr); - } - break; - case 0x2: // 32-bit write - if (Memory::IsValidAddress(addr)){ - InvalidateICache(addr & ~3, 4); - Memory::Write_U32((u32) arg, addr); - } - break; - case 0x3: // Increment/Decrement - { - addr = GetAddress(arg & 0x0FFFFFFF); - InvalidateICache(addr & ~3, 4); - value = 0; - int increment = 0; - // Read value from memory - switch ((comm >> 20) & 0xF) { - case 1: - case 2: // 8-bit - value = Memory::Read_U8(addr); - increment = comm & 0xFF; - break; - case 3: - case 4: // 16-bit - value = Memory::Read_U16(addr); - increment = comm & 0xFFFF; - break; - case 5: - case 6: // 32-bit - value = Memory::Read_U32(addr); - code = GetNextCode(); - if (code[0] != 0) { - increment = code[0]; - } - break; - } - // Increment/Decrement value - switch ((comm >> 20) & 0xF) { - case 1: - case 3: - case 5: // increment - value += increment; - break; - case 2: - case 4: - case 6: // Decrement - value -= increment; - break; - } - // Write value back to memory - switch ((comm >> 20) & 0xF) { - case 1: - case 2: // 8-bit - Memory::Write_U8((u8) value, addr); - break; - case 3: - case 4: // 16-bit - Memory::Write_U16((u16) value, addr); - break; - case 5: - case 6: // 32-bit - Memory::Write_U32((u32) value, addr); - break; - } - break; - } - case 0x4: // 32-bit patch code - code = GetNextCode(); - if (true) { - int data = code[0]; - int dataAdd = code[1]; + case 0x4: // Adress equal test. + case 0x5: // Address not equal test. + case 0x6: // Address less than test. + case 0x7: // Address greater than test. + addr = GetAddress(line1.part1 & 0x0FFFFFFF); + if (i < cheat.lines.size()) { + const CheatLine &line2 = cheat.lines[i++]; + const int sz = 1 << (line2.part2 & 0xF); - int count = (arg >> 16) & 0xFFFF; - int stepAddr = (arg & 0xFFFF) * 4; + CheatOp opcode; + switch (arg >> 28) { + case 0x4: + opcode = CheatOp::IfAddrEqual; + break; + case 0x5: + opcode = CheatOp::IfAddrNotEqual; + break; + case 0x6: + opcode = CheatOp::IfAddrLess; + break; + case 0x7: + opcode = CheatOp::IfAddrGreater; + break; + default: + return { CheatOp::Invalid }; + } - InvalidateICache(addr, count * stepAddr); - for (int a = 0; a < count; a++) { - if (Memory::IsValidAddress(addr)) { - Memory::Write_U32((u32) data, addr); - } - addr += stepAddr; - data += dataAdd; - } - } - break; - case 0x5: // Memcpy command - code = GetNextCode(); - if (true) { - int destAddr = GetAddress(code[0]); - int len = arg; - InvalidateICache(destAddr, len); - if (Memory::IsValidAddress(addr) && Memory::IsValidAddress(destAddr)) { - Memory::MemcpyUnchecked(destAddr, addr, len); - } - } - break; - case 0x6: // Pointer commands - code = GetNextCode(); - if (code.size() >= 2) { - int arg2 = code[0]; - int offset = code[1]; - int baseOffset = (arg2 >> 20) * 4; - InvalidateICache(addr + baseOffset, 4); - int base = Memory::Read_U32(addr + baseOffset); - int count = arg2 & 0xFFFF; - int type = (arg2 >> 16) & 0xF; - for (int i = 1; i < count; i ++ ) { - if (i < count) { - code = GetNextCode(); - if (code.size() < 2) { - // User provided incomplete cheat. Should warn but would be very spammy... - break; - } - int arg3 = code[0]; - int arg4 = code[1]; - int comm3 = arg3 >> 28; - switch (comm3) { - case 0x1: // type copy byte - { - int srcAddr = Memory::Read_U32(addr) + offset; - int dstAddr = Memory::Read_U32(addr + baseOffset) + (arg3 & 0x0FFFFFFF); - if (Memory::IsValidAddress(dstAddr) && Memory::IsValidAddress(srcAddr) && Memory::IsValidAddress(srcAddr + arg) && Memory::IsValidAddress(dstAddr + arg)) { - Memory::MemcpyUnchecked(dstAddr, srcAddr, arg); - } - type = -1; //Done - break; } - case 0x2: - case 0x3: // type pointer walk - { - int walkOffset = arg3 & 0x0FFFFFFF; - if (comm3 == 0x3) { - walkOffset = -walkOffset; - } - base = Memory::Read_U32(base + walkOffset); - int comm4 = arg4 >> 28; - switch (comm4) { - case 0x2: - case 0x3: // type pointer walk - walkOffset = arg4 & 0x0FFFFFFF; - if (comm4 == 0x3) { - walkOffset = -walkOffset; - } - base = Memory::Read_U32(base + walkOffset); - break; - } - break; } - case 0x9: // type multi address write - base += arg3 & 0x0FFFFFFF; - arg += arg4; - break; - default: - break; + CheatOperation op = { opcode, addr, sz, 0 }; + op.ifAddrTypes.skip = line2.part1; + op.ifAddrTypes.compareAddr = GetAddress(arg & 0x0FFFFFFF); + return op; + } + return { CheatOp::Invalid }; - } - } - } + default: + return { CheatOp::Invalid }; + } - switch (type) { - case 0: // 8 bit write - Memory::Write_U8((u8) arg, base + offset); - break; - case 1: // 16-bit write - Memory::Write_U16((u16) arg, base + offset); - break; - case 2: // 32-bit write - Memory::Write_U32((u32) arg, base + offset); - break; - case 3: // 8 bit inverse write - Memory::Write_U8((u8) arg, base - offset); - break; - case 4: // 16-bit inverse write - Memory::Write_U16((u16) arg, base - offset); - break; - case 5: // 32-bit inverse write - Memory::Write_U32((u32) arg, base - offset); - break; - case -1: // Operation already performed, nothing to do - break; - } - } - break; - case 0x7: // Boolean commands. - switch (arg >> 16) { - case 0x0000: // 8-bit OR. - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - int val1 = (int) (arg & 0xFF); - int val2 = (int) Memory::Read_U8(addr); - Memory::Write_U8((u8) (val1 | val2), addr); - } - break; - case 0x0002: // 8-bit AND. - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - int val1 = (int) (arg & 0xFF); - int val2 = (int) Memory::Read_U8(addr); - Memory::Write_U8((u8) (val1 & val2), addr); - } - break; - case 0x0004: // 8-bit XOR. - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - int val1 = (int) (arg & 0xFF); - int val2 = (int) Memory::Read_U8(addr); - Memory::Write_U8((u8) (val1 ^ val2), addr); - } - break; - case 0x0001: // 16-bit OR. - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - short val1 = (short) (arg & 0xFFFF); - short val2 = (short) Memory::Read_U16(addr); - Memory::Write_U16((u16) (val1 | val2), addr); - } - break; - case 0x0003: // 16-bit AND. - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - short val1 = (short) (arg & 0xFFFF); - short val2 = (short) Memory::Read_U16(addr); - Memory::Write_U16((u16) (val1 & val2), addr); - } - break; - case 0x0005: // 16-bit OR. - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr & ~3, 4); - short val1 = (short) (arg & 0xFFFF); - short val2 = (short) Memory::Read_U16(addr); - Memory::Write_U16((u16) (val1 ^ val2), addr); - } - break; - } - break; - case 0x8: // 8-bit and 16-bit patch code - code = GetNextCode(); - if (code.size() >= 2) { - int data = code[0]; - int dataAdd = code[1]; + case 0xE: // Multiple line skip tests. + addr = GetAddress(arg & 0x0FFFFFFF); + { + const bool is8Bit = (line1.part1 >> 24) == 0xE1; + const uint32_t val = is8Bit ? (line1.part1 & 0xFF) : (line1.part1 & 0xFFFF); - bool is8Bit = (data >> 16) == 0x0000; - int count = (arg >> 16) & 0xFFFF; - int stepAddr = (arg & 0xFFFF) * (is8Bit ? 1 : 2); - InvalidateICache(addr, count * stepAddr); - for (int a = 0; a < count; a++) { - if (Memory::IsValidAddress(addr)) { - if (is8Bit) { - Memory::Write_U8((u8) (data & 0xFF), addr); - } - else { - Memory::Write_U16((u16) (data & 0xFFFF), addr); - } - } - addr += stepAddr; - data += dataAdd; - } - } + CheatOp opcode; + switch (arg >> 28) { + case 0x0: + opcode = CheatOp::IfEqual; break; - case 0xB: // Time command (not sure what to do?) + case 0x1: + opcode = CheatOp::IfNotEqual; break; - case 0xC: // Code stopper - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr, 4); - value = Memory::Read_U32(addr); - if ((u32)value != arg) { - SkipAllCodes(); - } - } + case 0x2: + opcode = CheatOp::IfLess; + break; + case 0x3: + opcode = CheatOp::IfGreater; break; - case 0xD: // Test commands & Jocker codes - if ((arg >> 28) == 0x0 || (arg >> 28) == 0x2) { // 8Bit & 16Bit ignore next line cheat code - bool is8Bit = (arg >> 28) == 0x2; - addr = GetAddress(comm & 0x0FFFFFFF); - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr, 4); - int memoryValue = is8Bit ? Memory::Read_U8(addr) : Memory::Read_U16(addr); - int testValue = arg & (is8Bit ? 0xFF : 0xFFFF); - bool executeNextLines = false; - switch ((arg >> 20) & 0xF) { - case 0x0: // Equal - executeNextLines = memoryValue == testValue; - break; - case 0x1: // Not Equal - executeNextLines = memoryValue != testValue; - break; - case 0x2: // Less Than - executeNextLines = memoryValue < testValue; - break; - case 0x3: // Greater Than - executeNextLines = memoryValue > testValue; - break; - default: - break; - } - if (!executeNextLines) - SkipCodes(1); - } - break; - } - else if ((arg >> 28) == 0x1 || (arg >> 28) == 0x3) { // Buttons dependent ignore cheat code - // Button Code - // SELECT 0x00000001 - // START 0x00000008 - // DPAD UP 0x00000010 - // DPAD RIGHT 0x00000020 - // DPAD DOWN 0x00000040 - // DPAD LEFT 0x00000080 - // L TRIGGER 0x00000100 - // R TRIGGER 0x00000200 - // TRIANGLE 0x00001000 - // CIRCLE 0x00002000 - // CROSS 0x00004000 - // SQUARE 0x00008000 - // HOME 0x00010000 - // HOLD 0x00020000 - // WLAN 0x00040000 - // REMOTE HOLD 0x00080000 - // VOLUME UP 0x00100000 - // VOLUME DOWN 0x00200000 - // SCREEN 0x00400000 - // NOTE 0x00800000 - u32 buttonStatus = __CtrlPeekButtons(); - int skip = (comm & 0xFF) + 1; - u32 mask = arg & 0x0FFFFFFF; - if ((arg >> 28) == 0x1) - // Old, too specific check: if (buttonStatus == (arg & 0x0FFFFFFF)) // cheat code likes: 0xD00000nn 0x1bbbbbbb; - if ((buttonStatus & mask) == mask) // cheat code likes: 0xD00000nn 0x1bbbbbbb; - break; - else - SkipCodes(skip); - else // (arg >> 28) == 2? - // Old, too specific check: if (buttonStatus != (arg & 0x0FFFFFFF)) // cheat code likes: 0xD00000nn 0x3bbbbbbb; - if ((buttonStatus & mask) == mask) // cheat code likes: 0xD00000nn 0x3bbbbbbb; - SkipCodes(skip); - else - break; - break; - } - else if ((arg >> 28) == 0x4 || (arg >> 28) == 0x5 || (arg >> 28) == 0x6 || (arg >> 28) == 0x7) { - int addr1 = GetAddress(comm & 0x0FFFFFFF); - int addr2 = GetAddress(arg & 0x0FFFFFFF); - code = GetNextCode(); - if (code.size() >= 2) - if (Memory::IsValidAddress(addr1) && Memory::IsValidAddress(addr2)) { - int comm2 = code[0]; - int arg2 = code[1]; - int skip = (comm2 & 0xFFFFFFFF); - int memoryValue1 = 0; - int memoryValue2 = 0; - switch (arg2 & 0xF) { - case 0x0: // 8Bit - memoryValue1 = Memory::Read_U8(addr1); - memoryValue2 = Memory::Read_U8(addr2); - break; - case 0x1: // 16Bit - memoryValue1 = Memory::Read_U16(addr1); - memoryValue2 = Memory::Read_U16(addr2); - break; - case 0x2: // 32Bit - memoryValue1 = Memory::Read_U32(addr1); - memoryValue2 = Memory::Read_U32(addr2); - break; - default: - break; - } - switch (arg >> 28) { - case 0x4: // Equal - if (memoryValue1 != memoryValue2) - SkipCodes(skip); - break; - case 0x5: // Not Equal - if (memoryValue1 == memoryValue2) - SkipCodes(skip); - break; - case 0x6: // Less Than - if (memoryValue1 >= memoryValue2) - SkipCodes(skip); - break; - case 0x7: // Greater Than - if (memoryValue1 <= memoryValue2) - SkipCodes(skip); - break; - default: - break; - } - } - } - else - break; - case 0xE: // Test commands, multiple skip - { - bool is8Bit = (comm >> 24) == 0xE1; - addr = GetAddress(arg & 0x0FFFFFFF); - if (Memory::IsValidAddress(addr)) { - InvalidateICache(addr, 4); - int memoryValue = is8Bit ? Memory::Read_U8(addr) : Memory::Read_U16(addr); - int testValue = comm & (is8Bit ? 0xFF : 0xFFFF); - bool executeNextLines = false; - switch ( arg >> 28) { - case 0x0: // Equal - executeNextLines = memoryValue == testValue; - break; - case 0x1: // Not Equal - executeNextLines = memoryValue != testValue; - break; - case 0x2: // Less Than - executeNextLines = memoryValue < testValue; - break; - case 0x3: // Greater Than - executeNextLines = memoryValue > testValue; - break; - } - if (!executeNextLines) { - int skip = (comm >> 16) & (is8Bit ? 0xFF : 0xFFF); - SkipCodes(skip); - } - } - break; - } default: - { - break; + return { CheatOp::Invalid }; + } + + CheatOperation op = { opcode, addr, is8Bit ? 1 : 2, val }; + op.ifTypes.skip = (line1.part1 >> 16) & (is8Bit ? 0xFF : 0xFFF); + return op; + } + + default: + return { CheatOp::Invalid }; + } +} + +CheatOperation CWCheatEngine::InterpretNextTempAR(const CheatCode &cheat, size_t &i) { + // TODO + assert(false); + return { CheatOp::Invalid }; +} + +CheatOperation CWCheatEngine::InterpretNextOp(const CheatCode &cheat, size_t &i) { + if (cheat.fmt == CheatCodeFormat::CWCHEAT) + return InterpretNextCwCheat(cheat, i); + else if (cheat.fmt == CheatCodeFormat::TEMPAR) + return InterpretNextTempAR(cheat, i); + else + assert(false); + return { CheatOp::Invalid }; +} + +void CWCheatEngine::ApplyMemoryOperator(const CheatOperation &op, uint32_t(*oper)(uint32_t, uint32_t)) { + if (Memory::IsValidAddress(op.addr)) { + InvalidateICache(op.addr, 4); + if (op.sz == 1) + Memory::Write_U8((u8)oper(Memory::Read_U8(op.addr), op.val), op.addr); + else if (op.sz == 2) + Memory::Write_U16((u16)oper(Memory::Read_U16(op.addr), op.val),op. addr); + else if (op.sz == 4) + Memory::Write_U32((u32)oper(Memory::Read_U32(op.addr), op.val), op.addr); + } +} + +bool CWCheatEngine::TestIf(const CheatOperation &op, bool(*oper)(int, int)) { + if (Memory::IsValidAddress(op.addr)) { + InvalidateICache(op.addr, 4); + + int memoryValue = 0; + if (op.sz == 1) + memoryValue = (int)Memory::Read_U8(op.addr); + else if (op.sz == 2) + memoryValue = (int)Memory::Read_U16(op.addr); + else if (op.sz == 4) + memoryValue = (int)Memory::Read_U32(op.addr); + + return oper(memoryValue, (int)op.val); + } + return false; +} + +bool CWCheatEngine::TestIfAddr(const CheatOperation &op, bool(*oper)(int, int)) { + if (Memory::IsValidAddress(op.addr)) { + InvalidateICache(op.addr, 4); + + int memoryValue1 = 0; + int memoryValue2 = 0; + if (op.sz == 1) { + memoryValue1 = (int)Memory::Read_U8(op.addr); + memoryValue2 = (int)Memory::Read_U8(op.ifAddrTypes.compareAddr); + } else if (op.sz == 2) { + memoryValue1 = (int)Memory::Read_U16(op.addr); + memoryValue2 = (int)Memory::Read_U16(op.ifAddrTypes.compareAddr); + } else if (op.sz == 4) { + memoryValue1 = (int)Memory::Read_U32(op.addr); + memoryValue2 = (int)Memory::Read_U32(op.ifAddrTypes.compareAddr); + } + + return oper(memoryValue1, memoryValue2); + } + return false; +} + +void CWCheatEngine::ExecuteOp(const CheatOperation &op, const CheatCode &cheat, size_t &i) { + switch (op.op) { + case CheatOp::Invalid: + i = cheat.lines.size(); + break; + + case CheatOp::Noop: + break; + + case CheatOp::Write: + if (Memory::IsValidAddress(op.addr)) { + InvalidateICache(op.addr, 4); + if (op.sz == 1) + Memory::Write_U8((u8)op.val, op.addr); + else if (op.sz == 2) + Memory::Write_U16((u16)op.val, op.addr); + else if (op.sz == 4) + Memory::Write_U32((u32)op.val, op.addr); + } + break; + + case CheatOp::Add: + ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) { + return a + b; + }); + break; + + case CheatOp::Subtract: + ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) { + return a - b; + }); + break; + + case CheatOp::Or: + ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) { + return a | b; + }); + break; + + case CheatOp::And: + ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) { + return a & b; + }); + break; + + case CheatOp::Xor: + ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) { + return a ^ b; + }); + break; + + case CheatOp::MultiWrite: + if (Memory::IsValidAddress(op.addr)) { + InvalidateICache(op.addr, op.multiWrite.count * op.multiWrite.step + op.sz); + + uint32_t data = op.val; + uint32_t addr = op.addr; + for (uint32_t a = 0; a < op.multiWrite.count; a++) { + if (Memory::IsValidAddress(addr)) { + if (op.sz == 1) + Memory::Write_U8((u8)data, addr); + else if (op.sz == 2) + Memory::Write_U16((u16)data, addr); + else if (op.sz == 4) + Memory::Write_U32((u32)data, addr); } + addr += op.multiWrite.step; + data += op.multiWrite.add; } } + break; + + case CheatOp::CopyBytesFrom: + if (Memory::IsValidRange(op.addr, op.val) && Memory::IsValidRange(op.copyBytesFrom.destAddr, op.val)) { + InvalidateICache(op.addr, op.val); + InvalidateICache(op.copyBytesFrom.destAddr, op.val); + + Memory::MemcpyUnchecked(op.copyBytesFrom.destAddr, op.addr, op.val); + } + break; + + case CheatOp::Delay: + // TODO: Not supported. + break; + + case CheatOp::Assert: + if (Memory::IsValidAddress(op.addr)) { + InvalidateICache(op.addr, 4); + if (Memory::Read_U32(op.addr) != op.val) { + i = cheat.lines.size(); + } + } + break; + + case CheatOp::IfEqual: + if (!TestIf(op, [](int a, int b) { return a == b; })) { + i += (size_t)op.ifTypes.skip; + } + break; + + case CheatOp::IfNotEqual: + if (!TestIf(op, [](int a, int b) { return a != b; })) { + i += (size_t)op.ifTypes.skip; + } + break; + + case CheatOp::IfLess: + if (!TestIf(op, [](int a, int b) { return a < b; })) { + i += (size_t)op.ifTypes.skip; + } + break; + + case CheatOp::IfGreater: + if (!TestIf(op, [](int a, int b) { return a > b; })) { + i += (size_t)op.ifTypes.skip; + } + break; + + case CheatOp::IfAddrEqual: + if (!TestIf(op, [](int a, int b) { return a == b; })) { + i += (size_t)op.ifAddrTypes.skip; + } + break; + + case CheatOp::IfAddrNotEqual: + if (!TestIf(op, [](int a, int b) { return a != b; })) { + i += (size_t)op.ifAddrTypes.skip; + } + break; + + case CheatOp::IfAddrLess: + if (!TestIf(op, [](int a, int b) { return a < b; })) { + i += (size_t)op.ifAddrTypes.skip; + } + break; + + case CheatOp::IfAddrGreater: + if (!TestIf(op, [](int a, int b) { return a > b; })) { + i += (size_t)op.ifAddrTypes.skip; + } + break; + + case CheatOp::IfPressed: + // Button Code + // SELECT 0x00000001 + // START 0x00000008 + // DPAD UP 0x00000010 + // DPAD RIGHT 0x00000020 + // DPAD DOWN 0x00000040 + // DPAD LEFT 0x00000080 + // L TRIGGER 0x00000100 + // R TRIGGER 0x00000200 + // TRIANGLE 0x00001000 + // CIRCLE 0x00002000 + // CROSS 0x00004000 + // SQUARE 0x00008000 + // HOME 0x00010000 + // HOLD 0x00020000 + // WLAN 0x00040000 + // REMOTE HOLD 0x00080000 + // VOLUME UP 0x00100000 + // VOLUME DOWN 0x00200000 + // SCREEN 0x00400000 + // NOTE 0x00800000 + if ((__CtrlPeekButtons() & op.val) != op.val) { + i += (size_t)op.ifTypes.skip; + } + break; + + case CheatOp::IfNotPressed: + if ((__CtrlPeekButtons() & op.val) == op.val) { + i += (size_t)op.ifTypes.skip; + } + break; + + case CheatOp::CwCheatPointerCommands: + { + InvalidateICache(op.addr + op.pointerCommands.baseOffset, 4); + u32 base = Memory::Read_U32(op.addr + op.pointerCommands.baseOffset); + u32 val = op.val; + int type = op.pointerCommands.type; + for (int a = 0; a < op.pointerCommands.count; ++a) { + const CheatLine &line = cheat.lines[i++]; + switch (line.part1 >> 28) { + case 0x1: // type copy byte + { + InvalidateICache(op.addr, 4); + u32 srcAddr = Memory::Read_U32(op.addr) + op.pointerCommands.offset; + u32 dstAddr = Memory::Read_U32(op.addr + op.pointerCommands.baseOffset) + (line.part1 & 0x0FFFFFFF); + if (Memory::IsValidRange(dstAddr, val) && Memory::IsValidRange(srcAddr, val)) { + InvalidateICache(dstAddr, val); + InvalidateICache(srcAddr, val); + Memory::MemcpyUnchecked(dstAddr, srcAddr, val); + } + // Don't perform any further action. + type = -1; + } + break; + + case 0x2: + case 0x3: // type pointer walk + { + int walkOffset = (int)line.part1 & 0x0FFFFFFF; + if ((line.part1 >> 28) == 0x3) { + walkOffset = -walkOffset; + } + InvalidateICache(base + walkOffset, 4); + base = Memory::Read_U32(base + walkOffset); + switch (line.part2 >> 28) { + case 0x2: + case 0x3: // type pointer walk + walkOffset = line.part2 & 0x0FFFFFFF; + if ((line.part2 >> 28) == 0x3) { + walkOffset = -walkOffset; + } + InvalidateICache(base + walkOffset, 4); + base = Memory::Read_U32(base + walkOffset); + break; + + default: + // Unexpected value in cheat line? + break; + } + } + break; + + case 0x9: // type multi address write + base += line.part1 & 0x0FFFFFFF; + val += line.part2; + break; + + default: + // Unexpected value in cheat line? + break; + } + } + + switch (type) { + case 0: // 8 bit write + InvalidateICache(base + op.pointerCommands.offset, 4); + Memory::Write_U8((u8)val, base + op.pointerCommands.offset); + break; + case 1: // 16-bit write + InvalidateICache(base + op.pointerCommands.offset, 4); + Memory::Write_U16((u16)val, base + op.pointerCommands.offset); + break; + case 2: // 32-bit write + InvalidateICache(base + op.pointerCommands.offset, 4); + Memory::Write_U32((u32)val, base + op.pointerCommands.offset); + break; + case 3: // 8 bit inverse write + InvalidateICache(base - op.pointerCommands.offset, 4); + Memory::Write_U8((u8)val, base - op.pointerCommands.offset); + break; + case 4: // 16-bit inverse write + InvalidateICache(base - op.pointerCommands.offset, 4); + Memory::Write_U16((u16)val, base - op.pointerCommands.offset); + break; + case 5: // 32-bit inverse write + InvalidateICache(base - op.pointerCommands.offset, 4); + Memory::Write_U32((u32)val, base - op.pointerCommands.offset); + break; + case -1: // Operation already performed, nothing to do + break; + } + } + break; + + default: + assert(false); + } +} + +void CWCheatEngine::Run() { + for (CheatCode cheat : cheats_) { + // InterpretNextOp and ExecuteOp move i. + for (size_t i = 0; i < cheat.lines.size(); ) { + CheatOperation op = InterpretNextOp(cheat, i); + ExecuteOp(op, cheat, i); + } } - // exiting... - Exit(); } bool CWCheatEngine::HasCheats() { - return !parts.empty(); + return !cheats_.empty(); } bool CheatsInEffect() { diff --git a/Core/CwCheat.h b/Core/CwCheat.h index be8b27be9f..cef0002289 100644 --- a/Core/CwCheat.h +++ b/Core/CwCheat.h @@ -18,25 +18,45 @@ void __CheatDoState(PointerWrap &p); // Return whether cheats are enabled and in effect. bool CheatsInEffect(); +struct CheatLine { + uint32_t part1; + uint32_t part2; +}; + +enum class CheatCodeFormat { + UNDEFINED, + CWCHEAT, + TEMPAR, +}; + +struct CheatCode { + CheatCodeFormat fmt; + std::vector lines; +}; + +struct CheatOperation; + class CWCheatEngine { public: CWCheatEngine(); std::vector GetCodesList(); - void CreateCodeList(); + void ParseCheats(); void CreateCheatFile(); - void Exit(); void Run(); - std::vector GetNextCode(); bool HasCheats(); private: void InvalidateICache(u32 addr, int size); - void SkipCodes(int count); - void SkipAllCodes(); - bool exit2, cheatEnabled; - int GetAddress(int value); - std::vector codeNameList; + u32 GetAddress(u32 value); - std::vector codes, initialCodesList, parts; - size_t currentCode; + CheatOperation InterpretNextOp(const CheatCode &cheat, size_t &i); + CheatOperation InterpretNextCwCheat(const CheatCode &cheat, size_t &i); + CheatOperation InterpretNextTempAR(const CheatCode &cheat, size_t &i); + + void ExecuteOp(const CheatOperation &op, const CheatCode &cheat, size_t &i); + void ApplyMemoryOperator(const CheatOperation &op, uint32_t(*oper)(uint32_t, uint32_t)); + bool TestIf(const CheatOperation &op, bool(*oper)(int a, int b)); + bool TestIfAddr(const CheatOperation &op, bool(*oper)(int a, int b)); + + std::vector cheats_; }; diff --git a/Core/HLE/sceGe.cpp b/Core/HLE/sceGe.cpp index 25aaa6848a..fad468bfa8 100644 --- a/Core/HLE/sceGe.cpp +++ b/Core/HLE/sceGe.cpp @@ -57,10 +57,6 @@ static int geSyncEvent; static int geInterruptEvent; static int geCycleEvent; -// Let's try updating 10 times per vblank - this is the interval for geCycleEvent. -const int geIntervalUs = 1000000 / (60 * 10); -const int geBehindThresholdUs = 1000000 / (60 * 10); - class GeIntrHandler : public IntrHandler { public: GeIntrHandler() : IntrHandler(PSP_GE_INTR) {} @@ -194,19 +190,7 @@ static void __GeExecuteInterrupt(u64 userdata, int cyclesLate) { } static void __GeCheckCycles(u64 userdata, int cyclesLate) { - u64 geTicks = gpu->GetTickEstimate(); - if (geTicks != 0) { - if (CoreTiming::GetTicks() > geTicks + usToCycles(geBehindThresholdUs)) { - u64 diff = CoreTiming::GetTicks() - geTicks; - // Let the GPU thread catch up. - gpu->SyncThread(); - CoreTiming::Advance(); - } - } - - // This may get out of step if we synced (because we don't correct for cyclesLate), - // but that's okay - __GeCheckCycles is a very rough way to synchronize anyway. - CoreTiming::ScheduleEvent(usToCycles(geIntervalUs), geCycleEvent, 0); + // Deprecated } void __GeInit() { @@ -217,15 +201,12 @@ void __GeInit() { geSyncEvent = CoreTiming::RegisterEvent("GeSyncEvent", &__GeExecuteSync); geInterruptEvent = CoreTiming::RegisterEvent("GeInterruptEvent", &__GeExecuteInterrupt); + + // Deprecated geCycleEvent = CoreTiming::RegisterEvent("GeCycleEvent", &__GeCheckCycles); listWaitingThreads.clear(); drawWaitingThreads.clear(); - - // When we're using separate CPU/GPU threads, we need to keep them in sync. - if (IsOnSeparateCPUThread()) { - CoreTiming::ScheduleEvent(usToCycles(geIntervalUs), geCycleEvent, 0); - } } struct GeInterruptData_v1 { @@ -270,20 +251,18 @@ void __GeDoState(PointerWrap &p) { void __GeShutdown() { } -// Warning: may be called from the GPU thread, if there is a separate one (multithread mode). bool __GeTriggerSync(GPUSyncType type, int id, u64 atTicks) { u64 userdata = (u64)id << 32 | (u64)type; s64 future = atTicks - CoreTiming::GetTicks(); if (type == GPU_SYNC_DRAW) { - s64 left = CoreTiming::UnscheduleThreadsafeEvent(geSyncEvent, userdata); + s64 left = CoreTiming::UnscheduleEvent(geSyncEvent, userdata); if (left > future) future = left; } - CoreTiming::ScheduleEvent_Threadsafe(future, geSyncEvent, userdata); + CoreTiming::ScheduleEvent(future, geSyncEvent, userdata); return true; } -// Warning: may be called from the GPU thread, if there is a separate one (multithread mode). bool __GeTriggerInterrupt(int listid, u32 pc, u64 atTicks) { GeInterruptData intrdata; intrdata.listid = listid; @@ -293,7 +272,7 @@ bool __GeTriggerInterrupt(int listid, u32 pc, u64 atTicks) { ge_pending_cb.push_back(intrdata); u64 userdata = (u64)listid << 32 | (u64) pc; - CoreTiming::ScheduleEvent_Threadsafe(atTicks - CoreTiming::GetTicks(), geInterruptEvent, userdata); + CoreTiming::ScheduleEvent(atTicks - CoreTiming::GetTicks(), geInterruptEvent, userdata); return true; } @@ -504,7 +483,6 @@ static int sceGeUnsetCallback(u32 cbID) { // unless some insane game pokes it and relies on it... u32 sceGeSaveContext(u32 ctxAddr) { DEBUG_LOG(SCEGE, "sceGeSaveContext(%08x)", ctxAddr); - gpu->SyncThread(); if (gpu->BusyDrawing()) { WARN_LOG(SCEGE, "sceGeSaveContext(%08x): lists in process, aborting", ctxAddr); @@ -524,7 +502,6 @@ u32 sceGeSaveContext(u32 ctxAddr) { u32 sceGeRestoreContext(u32 ctxAddr) { DEBUG_LOG(SCEGE, "sceGeRestoreContext(%08x)", ctxAddr); - gpu->SyncThread(); if (gpu->BusyDrawing()) { WARN_LOG(SCEGE, "sceGeRestoreContext(%08x): lists in process, aborting", ctxAddr); diff --git a/Core/HLE/sceUsbCam.h b/Core/HLE/sceUsbCam.h index 14aff22212..ef088fe91d 100644 --- a/Core/HLE/sceUsbCam.h +++ b/Core/HLE/sceUsbCam.h @@ -46,8 +46,7 @@ typedef struct PspUsbCamSetupVideoParam { int evlevel; } PspUsbCamSetupVideoParam; - -static unsigned char sceUsbCamDummyImage[] = { +static const unsigned char sceUsbCamDummyImage[] = { 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0xFF, 0xE1, 0x00, 0x68, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x1A, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index 992c44eee4..00eb86f893 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -129,6 +129,8 @@ namespace MIPSComp { } } + DISABLE; + u32 iaddr = gpr.IsImm(rs) ? offset + gpr.GetImm(rs) : 0xFFFFFFFF; std::vector skips; diff --git a/Core/System.cpp b/Core/System.cpp index 04c93deeb1..1030d914ee 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -328,7 +328,6 @@ void CPU_RunLoop() { switch (cpuThreadState) { case CPU_THREAD_EXECUTE: mipsr4k.RunLoopUntil(cpuThreadUntil); - gpu->FinishEventLoop(); CPU_NextState(CPU_THREAD_EXECUTE, CPU_THREAD_RUNNING); break; @@ -354,12 +353,6 @@ void CPU_RunLoop() { coreState = CORE_POWERDOWN; } - // Let's make sure the gpu has already cleaned up before we start freeing memory. - if (gpu) { - gpu->FinishEventLoop(); - gpu->SyncThread(true); - } - CPU_Shutdown(); CPU_SetState(CPU_THREAD_NOT_RUNNING); } @@ -371,9 +364,9 @@ void Core_UpdateState(CoreState newState) { Core_UpdateSingleStep(); } -static void Core_UpdateDebugStats(bool flag) { - if (coreCollectDebugStats != flag) { - coreCollectDebugStats = flag; +void Core_UpdateDebugStats(bool collectStats) { + if (coreCollectDebugStats != collectStats) { + coreCollectDebugStats = collectStats; mipsr4k.ClearJitCache(); } @@ -384,18 +377,15 @@ static void Core_UpdateDebugStats(bool flag) { void System_Wake() { // Ping the threads so they check coreState. CPU_NextStateNot(CPU_THREAD_NOT_RUNNING, CPU_THREAD_SHUTDOWN); - if (gpu) { - gpu->FinishEventLoop(); - } } // Ugly! static bool pspIsInited = false; static bool pspIsIniting = false; -static bool pspIsQuiting = false; +static bool pspIsQuitting = false; bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { - if (pspIsIniting || pspIsQuiting) { + if (pspIsIniting || pspIsQuitting) { return false; } @@ -415,7 +405,9 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { coreParameter.errorString = ""; pspIsIniting = true; - if (g_Config.bSeparateCPUThread) { + // Keeping this around because we might need it in the future. + const bool separateCPUThread = false; + if (separateCPUThread) { Core_ListenShutdown(System_Wake); CPU_SetState(CPU_THREAD_PENDING); cpuThread = new std::thread(&CPU_RunLoop); @@ -438,7 +430,7 @@ bool PSP_InitUpdate(std::string *error_string) { return true; } - if (g_Config.bSeparateCPUThread && !CPU_IsReady()) { + if (!CPU_IsReady()) { return false; } @@ -459,7 +451,8 @@ bool PSP_InitUpdate(std::string *error_string) { bool PSP_Init(const CoreParameter &coreParam, std::string *error_string) { PSP_InitStart(coreParam, error_string); - if (g_Config.bSeparateCPUThread) { + // For a potential resurrection of separate CPU thread later. + if (false) { CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsReady); } @@ -472,12 +465,12 @@ bool PSP_IsIniting() { } bool PSP_IsInited() { - return pspIsInited && !pspIsQuiting; + return pspIsInited && !pspIsQuitting; } void PSP_Shutdown() { // Do nothing if we never inited. - if (!pspIsInited && !pspIsIniting && !pspIsQuiting) { + if (!pspIsInited && !pspIsIniting && !pspIsQuitting) { return; } @@ -488,7 +481,7 @@ void PSP_Shutdown() { #endif // Make sure things know right away that PSP memory, etc. is going away. - pspIsQuiting = true; + pspIsQuitting = true; if (coreState == CORE_RUNNING) Core_UpdateState(CORE_ERROR); Core_NotifyShutdown(); @@ -507,7 +500,7 @@ void PSP_Shutdown() { currentMIPS = 0; pspIsInited = false; pspIsIniting = false; - pspIsQuiting = false; + pspIsQuitting = false; g_Config.unloadGameConfig(); } @@ -525,15 +518,14 @@ void PSP_EndHostFrame() { } void PSP_RunLoopUntil(u64 globalticks) { - Core_UpdateDebugStats(g_Config.bShowDebugStats || g_Config.bLogFrameDrops); - SaveState::Process(); if (coreState == CORE_POWERDOWN || coreState == CORE_ERROR) { return; } - // Switch the CPU thread on or off, as the case may be. - bool useCPUThread = g_Config.bSeparateCPUThread; + // We no longer allow a separate CPU thread but if we add a render queue + // to GL we're gonna need it. + bool useCPUThread = false; if (useCPUThread && cpuThread == nullptr) { // Need to start the cpu thread. Core_ListenShutdown(System_Wake); @@ -541,9 +533,8 @@ void PSP_RunLoopUntil(u64 globalticks) { cpuThread = new std::thread(&CPU_RunLoop); cpuThreadID = cpuThread->get_id(); cpuThread->detach(); - if (gpu) { - gpu->SetThreadEnabled(true); - } + // Probably needs to tell the gpu that it will need to queue up its output + // on another thread. CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsReady); } else if (!useCPUThread && cpuThread != nullptr) { CPU_SetState(CPU_THREAD_QUIT); @@ -551,21 +542,15 @@ void PSP_RunLoopUntil(u64 globalticks) { delete cpuThread; cpuThread = nullptr; cpuThreadID = std::thread::id(); - if (gpu) { - gpu->SetThreadEnabled(false); - } } if (cpuThread != nullptr) { - // Tell the gpu a new frame is about to begin, before we start the CPU. - gpu->SyncBeginFrame(); - cpuThreadUntil = globalticks; if (CPU_NextState(CPU_THREAD_RUNNING, CPU_THREAD_EXECUTE)) { // The CPU doesn't actually respect cpuThreadUntil well, especially when skipping frames. // TODO: Something smarter? Or force CPU to bail periodically? while (!CPU_IsReady()) { - gpu->RunEventsUntil(CoreTiming::GetTicks() + msToCycles(1000)); + // Have the GPU do stuff here. if (coreState != CORE_RUNNING) { CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsReady); } diff --git a/Core/System.h b/Core/System.h index 338d679fe1..26d1d6e19b 100644 --- a/Core/System.h +++ b/Core/System.h @@ -73,6 +73,9 @@ void PSP_EndHostFrame(); void PSP_RunLoopUntil(u64 globalticks); void PSP_RunLoopFor(int cycles); +// Call before PSP_BeginHostFrame() in order to not miss any GPU stats. +void Core_UpdateDebugStats(bool collectStats); + void Audio_Init(); bool IsOnSeparateCPUThread(); diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index 86acf93c2b..b8ad7bb896 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -1932,6 +1932,7 @@ bool FramebufferManagerCommon::GetFramebuffer(u32 fb_address, int fb_stride, GEB bool flipY = (g_Config.iGPUBackend == GPU_BACKEND_OPENGL && !useBufferedRendering_) ? true : false; buffer.Allocate(w, h, GE_FORMAT_8888, flipY, true); bool retval = draw_->CopyFramebufferToMemorySync(bound, Draw::FB_COLOR_BIT, 0, 0, w, h, Draw::DataFormat::R8G8B8A8_UNORM, buffer.GetData(), w); + gpuStats.numReadbacks++; // Vulkan requires us to re-apply all dynamic state for each command buffer, and the above will cause us to start a new cmdbuf. // So let's dirty the things that are involved in Vulkan dynamic state. Readbacks are not frequent so this won't hurt other backends. gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE); @@ -2034,6 +2035,7 @@ void FramebufferManagerCommon::PackFramebufferSync_(VirtualFramebuffer *vfb, int DEBUG_LOG(G3D, "Reading framebuffer to mem, fb_address = %08x", fb_address); draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, x, y, w, h, destFormat, destPtr, vfb->fb_stride); + gpuStats.numReadbacks++; } void FramebufferManagerCommon::ReadFramebufferToMemory(VirtualFramebuffer *vfb, bool sync, int x, int y, int w, int h) { @@ -2084,7 +2086,7 @@ void FramebufferManagerCommon::DownloadFramebufferForClut(u32 fb_address, u32 lo FlushBeforeCopy(); // No need to download if we already have it. - if (!vfb->memoryUpdated && vfb->clutUpdatedBytes < loadBytes) { + if (w > 0 && h > 0 && !vfb->memoryUpdated && vfb->clutUpdatedBytes < loadBytes) { // We intentionally don't call OptimizeDownloadRange() here - we don't want to over download. // CLUT framebuffers are often incorrectly estimated in size. if (x == 0 && y == 0 && w == vfb->width && h == vfb->height) { diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index bab715e13f..f55c793c47 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -400,6 +400,7 @@ void SoftwareTransform( // rectangle out of many. Quite a small optimization though. // Experiment: Disable on PowerVR (see issue #6290) // TODO: This bleeds outside the play area in non-buffered mode. Big deal? Probably not. + // TODO: Allow creating a depth clear and a color draw. bool reallyAClear = false; if (maxIndex > 1 && prim == GE_PRIM_RECTANGLES && gstate.isModeClear()) { int scissorX2 = gstate.getScissorX2() + 1; diff --git a/GPU/D3D11/DrawEngineD3D11.h b/GPU/D3D11/DrawEngineD3D11.h index 7175a78e61..48b52063bf 100644 --- a/GPU/D3D11/DrawEngineD3D11.h +++ b/GPU/D3D11/DrawEngineD3D11.h @@ -17,6 +17,7 @@ #pragma once +#include #include #include @@ -190,7 +191,8 @@ private: PushBufferD3D11 *pushVerts_; PushBufferD3D11 *pushInds_; - // D3D11 state object caches + // D3D11 state object caches. + // TODO: Change them to DenseHashMaps. std::map blendCache_; std::map blendCache1_; std::map depthStencilCache_; diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index cc3f06f16e..057716bf00 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -286,7 +286,7 @@ void GPU_D3D11::DeviceRestore() { // Nothing needed. } -void GPU_D3D11::InitClearInternal() { +void GPU_D3D11::InitClear() { bool useNonBufferedRendering = g_Config.iRenderingMode == FB_NON_BUFFERED_MODE; if (useNonBufferedRendering) { // device_->Clear(0, NULL, D3DCLEAR_STENCIL | D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.f, 0); @@ -305,8 +305,8 @@ void GPU_D3D11::BeginHostFrame() { } } -void GPU_D3D11::ReapplyGfxStateInternal() { - GPUCommon::ReapplyGfxStateInternal(); +void GPU_D3D11::ReapplyGfxState() { + GPUCommon::ReapplyGfxState(); // TODO: Dirty our caches for depth states etc } @@ -316,8 +316,8 @@ void GPU_D3D11::EndHostFrame() { draw_->BindPipeline(nullptr); } -void GPU_D3D11::BeginFrameInternal() { - GPUCommon::BeginFrameInternal(); +void GPU_D3D11::BeginFrame() { + GPUCommon::BeginFrame(); textureCacheD3D11_->StartFrame(); drawEngine_.BeginFrame(); @@ -338,13 +338,6 @@ void GPU_D3D11::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat f } bool GPU_D3D11::FramebufferDirty() { - // FIXME: Workaround for displaylists sometimes hanging unprocessed. Not yet sure of the cause. - if (ThreadEnabled()) { - // FIXME: Workaround for displaylists sometimes hanging unprocessed. Not yet sure of the cause. - ScheduleEvent(GPU_EVENT_PROCESS_QUEUE); - // Allow it to process fully before deciding if it's dirty. - SyncThread(); - } VirtualFramebuffer *vfb = framebufferManager_->GetDisplayVFB(); if (vfb) { bool dirty = vfb->dirtyAfterDisplay; @@ -353,15 +346,8 @@ bool GPU_D3D11::FramebufferDirty() { } return true; } -bool GPU_D3D11::FramebufferReallyDirty() { - // FIXME: Workaround for displaylists sometimes hanging unprocessed. Not yet sure of the cause. - if (ThreadEnabled()) { - // FIXME: Workaround for displaylists sometimes hanging unprocessed. Not yet sure of the cause. - ScheduleEvent(GPU_EVENT_PROCESS_QUEUE); - // Allow it to process fully before deciding if it's dirty. - SyncThread(); - } +bool GPU_D3D11::FramebufferReallyDirty() { VirtualFramebuffer *vfb = framebufferManager_->GetDisplayVFB(); if (vfb) { bool dirty = vfb->reallyDirtyAfterDisplay; @@ -371,7 +357,7 @@ bool GPU_D3D11::FramebufferReallyDirty() { return true; } -void GPU_D3D11::CopyDisplayToOutputInternal() { +void GPU_D3D11::CopyDisplayToOutput() { float blendColor[4]{}; context_->OMSetBlendState(stockD3D11.blendStateDisabledWithColorMask[0xF], blendColor, 0xFFFFFFFF); @@ -727,6 +713,7 @@ void GPU_D3D11::GetStats(char *buffer, size_t bufsize) { "Cached, Uncached Vertices Drawn: %i, %i\n" "FBOs active: %i\n" "Textures active: %i, decoded: %i invalidated: %i\n" + "Readbacks: %d\n" "Vertex, Fragment shaders loaded: %i, %i\n", gpuStats.msProcessingDisplayLists * 1000.0f, gpuStats.numDrawCalls, @@ -743,6 +730,7 @@ void GPU_D3D11::GetStats(char *buffer, size_t bufsize) { (int)textureCacheD3D11_->NumLoadedTextures(), gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, + gpuStats.numReadbacks, shaderManagerD3D11_->GetNumVertexShaders(), shaderManagerD3D11_->GetNumFragmentShaders() ); diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index c83bc35534..a528d41169 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -40,7 +40,7 @@ public: void PreExecuteOp(u32 op, u32 diff) override; void ExecuteOp(u32 op, u32 diff) override; - void ReapplyGfxStateInternal() override; + void ReapplyGfxState() override; void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; void GetStats(char *buffer, size_t bufsize) override; void ClearCacheNextFrame() override; @@ -96,9 +96,9 @@ private: void CheckFlushOp(int cmd, u32 diff); void BuildReportingInfo(); - void InitClearInternal() override; - void BeginFrameInternal() override; - void CopyDisplayToOutputInternal() override; + void InitClear() override; + void BeginFrame() override; + void CopyDisplayToOutput() override; ID3D11Device *device_; ID3D11DeviceContext *context_; diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index 00b7693ce3..358a6411ef 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -203,7 +203,6 @@ void TextureCacheD3D11::SetFramebufferSamplingParams(u16 bufferWidth, u16 buffer bool sClamp; bool tClamp; float lodBias; - bool autoMip; GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, 0, 0); key.minFilt = minFilt & 1; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 0dd93e7137..7459d45df6 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -251,7 +251,7 @@ void GPU_DX9::DeviceRestore() { // Nothing needed. } -void GPU_DX9::InitClearInternal() { +void GPU_DX9::InitClear() { bool useNonBufferedRendering = g_Config.iRenderingMode == FB_NON_BUFFERED_MODE; if (useNonBufferedRendering) { dxstate.depthWrite.set(true); @@ -272,12 +272,12 @@ void GPU_DX9::BeginHostFrame() { } } -void GPU_DX9::ReapplyGfxStateInternal() { +void GPU_DX9::ReapplyGfxState() { dxstate.Restore(); - GPUCommon::ReapplyGfxStateInternal(); + GPUCommon::ReapplyGfxState(); } -void GPU_DX9::BeginFrameInternal() { +void GPU_DX9::BeginFrame() { // Turn off vsync when unthrottled int desiredVSyncInterval = g_Config.bVSync ? 1 : 0; if ((PSP_CoreParameter().unthrottle) || (PSP_CoreParameter().fpsLimit == 1)) @@ -292,7 +292,7 @@ void GPU_DX9::BeginFrameInternal() { depalShaderCache_.Decimate(); // fragmentTestCache_.Decimate(); - GPUCommon::BeginFrameInternal(); + GPUCommon::BeginFrame(); shaderManagerDX9_->DirtyShader(); framebufferManager_->BeginFrame(); @@ -304,13 +304,6 @@ void GPU_DX9::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat for } bool GPU_DX9::FramebufferDirty() { - // FIXME: Workaround for displaylists sometimes hanging unprocessed. Not yet sure of the cause. - if (ThreadEnabled()) { - // FIXME: Workaround for displaylists sometimes hanging unprocessed. Not yet sure of the cause. - ScheduleEvent(GPU_EVENT_PROCESS_QUEUE); - // Allow it to process fully before deciding if it's dirty. - SyncThread(); - } VirtualFramebuffer *vfb = framebufferManager_->GetDisplayVFB(); if (vfb) { bool dirty = vfb->dirtyAfterDisplay; @@ -319,15 +312,8 @@ bool GPU_DX9::FramebufferDirty() { } return true; } -bool GPU_DX9::FramebufferReallyDirty() { - // FIXME: Workaround for displaylists sometimes hanging unprocessed. Not yet sure of the cause. - if (ThreadEnabled()) { - // FIXME: Workaround for displaylists sometimes hanging unprocessed. Not yet sure of the cause. - ScheduleEvent(GPU_EVENT_PROCESS_QUEUE); - // Allow it to process fully before deciding if it's dirty. - SyncThread(); - } +bool GPU_DX9::FramebufferReallyDirty() { VirtualFramebuffer *vfb = framebufferManager_->GetDisplayVFB(); if (vfb) { bool dirty = vfb->reallyDirtyAfterDisplay; @@ -337,7 +323,7 @@ bool GPU_DX9::FramebufferReallyDirty() { return true; } -void GPU_DX9::CopyDisplayToOutputInternal() { +void GPU_DX9::CopyDisplayToOutput() { dxstate.depthWrite.set(true); dxstate.colorMask.set(true, true, true, true); @@ -659,6 +645,7 @@ void GPU_DX9::GetStats(char *buffer, size_t bufsize) { "Cached, Uncached Vertices Drawn: %i, %i\n" "FBOs active: %i\n" "Textures active: %i, decoded: %i invalidated: %i\n" + "Readbacks: %d\n" "Vertex, Fragment shaders loaded: %i, %i\n", gpuStats.msProcessingDisplayLists * 1000.0f, gpuStats.numDrawCalls, @@ -675,6 +662,7 @@ void GPU_DX9::GetStats(char *buffer, size_t bufsize) { (int)textureCacheDX9_->NumLoadedTextures(), gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, + gpuStats.numReadbacks, shaderManagerDX9_->GetNumVertexShaders(), shaderManagerDX9_->GetNumFragmentShaders() ); diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 72e76267ff..2f15d48197 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -41,7 +41,7 @@ public: void PreExecuteOp(u32 op, u32 diff) override; void ExecuteOp(u32 op, u32 diff) override; - void ReapplyGfxStateInternal() override; + void ReapplyGfxState() override; void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; void GetStats(char *buffer, size_t bufsize) override; void ClearCacheNextFrame() override; @@ -96,9 +96,9 @@ private: void CheckFlushOp(int cmd, u32 diff); void BuildReportingInfo(); - void InitClearInternal() override; - void BeginFrameInternal() override; - void CopyDisplayToOutputInternal() override; + void InitClear() override; + void BeginFrame() override; + void CopyDisplayToOutput() override; LPDIRECT3DDEVICE9 device_; LPDIRECT3DDEVICE9EX deviceEx_; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index b7d25debd3..b8e7adbd06 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -401,13 +401,14 @@ void GPU_GLES::DeviceRestore() { UpdateVsyncInterval(true); } -void GPU_GLES::ReinitializeInternal() { +void GPU_GLES::Reinitialize() { + GPUCommon::Reinitialize(); textureCacheGL_->Clear(true); depalShaderCache_.Clear(); framebufferManagerGL_->DestroyAllFBOs(); } -void GPU_GLES::InitClearInternal() { +void GPU_GLES::InitClear() { bool useNonBufferedRendering = g_Config.iRenderingMode == FB_NON_BUFFERED_MODE; if (useNonBufferedRendering) { glstate.depthWrite.set(GL_TRUE); @@ -467,13 +468,13 @@ void GPU_GLES::UpdateCmdInfo() { } } -void GPU_GLES::ReapplyGfxStateInternal() { +void GPU_GLES::ReapplyGfxState() { drawEngine_.RestoreVAO(); glstate.Restore(); - GPUCommon::ReapplyGfxStateInternal(); + GPUCommon::ReapplyGfxState(); } -void GPU_GLES::BeginFrameInternal() { +void GPU_GLES::BeginFrame() { UpdateVsyncInterval(resized_); resized_ = false; @@ -483,7 +484,7 @@ void GPU_GLES::BeginFrameInternal() { depalShaderCache_.Decimate(); fragmentTestCache_.Decimate(); - GPUCommon::BeginFrameInternal(); + GPUCommon::BeginFrame(); // Save the cache from time to time. TODO: How often? if (!shaderCachePath_.empty() && (gpuStats.numFlips & 1023) == 0) { @@ -504,11 +505,6 @@ void GPU_GLES::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat fo } bool GPU_GLES::FramebufferDirty() { - if (ThreadEnabled()) { - // Allow it to process fully before deciding if it's dirty. - SyncThread(); - } - VirtualFramebuffer *vfb = framebufferManagerGL_->GetDisplayVFB(); if (vfb) { bool dirty = vfb->dirtyAfterDisplay; @@ -519,11 +515,6 @@ bool GPU_GLES::FramebufferDirty() { } bool GPU_GLES::FramebufferReallyDirty() { - if (ThreadEnabled()) { - // Allow it to process fully before deciding if it's dirty. - SyncThread(); - } - VirtualFramebuffer *vfb = framebufferManagerGL_->GetDisplayVFB(); if (vfb) { bool dirty = vfb->reallyDirtyAfterDisplay; @@ -533,7 +524,7 @@ bool GPU_GLES::FramebufferReallyDirty() { return true; } -void GPU_GLES::CopyDisplayToOutputInternal() { +void GPU_GLES::CopyDisplayToOutput() { // Flush anything left over. framebufferManagerGL_->RebindFramebuffer(); drawEngine_.Flush(); @@ -893,6 +884,7 @@ void GPU_GLES::GetStats(char *buffer, size_t bufsize) { "Cached, Uncached Vertices Drawn: %i, %i\n" "FBOs active: %i\n" "Textures active: %i, decoded: %i invalidated: %i\n" + "Readbacks: %d\n" "Vertex, Fragment, Programs loaded: %i, %i, %i\n", gpuStats.msProcessingDisplayLists * 1000.0f, gpuStats.numDrawCalls, @@ -909,6 +901,7 @@ void GPU_GLES::GetStats(char *buffer, size_t bufsize) { (int)textureCacheGL_->NumLoadedTextures(), gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, + gpuStats.numReadbacks, shaderManagerGL_->GetNumVertexShaders(), shaderManagerGL_->GetNumFragmentShaders(), shaderManagerGL_->GetNumPrograms()); diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index e15a26c4d8..a9662cf271 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -41,7 +41,7 @@ public: void PreExecuteOp(u32 op, u32 diff) override; void ExecuteOp(u32 op, u32 diff) override; - void ReapplyGfxStateInternal() override; + void ReapplyGfxState() override; void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; void GetStats(char *buffer, size_t bufsize) override; @@ -95,10 +95,10 @@ private: void CheckFlushOp(int cmd, u32 diff); void BuildReportingInfo(); - void InitClearInternal() override; - void BeginFrameInternal() override; - void CopyDisplayToOutputInternal() override; - void ReinitializeInternal() override; + void InitClear() override; + void BeginFrame() override; + void CopyDisplayToOutput() override; + void Reinitialize() override; inline void UpdateVsyncInterval(bool force); void UpdateCmdInfo(); diff --git a/GPU/GPU.h b/GPU/GPU.h index 2d9435da46..2042fab015 100644 --- a/GPU/GPU.h +++ b/GPU/GPU.h @@ -67,6 +67,7 @@ struct GPUStatistics { numShaderSwitches = 0; numFlushes = 0; numTexturesDecoded = 0; + numReadbacks = 0; msProcessingDisplayLists = 0; vertexGPUCycles = 0; otherGPUCycles = 0; @@ -85,6 +86,7 @@ struct GPUStatistics { int numTextureSwitches; int numShaderSwitches; int numTexturesDecoded; + int numReadbacks; double msProcessingDisplayLists; int vertexGPUCycles; int otherGPUCycles; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 2704375db6..3cfaf9badb 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -359,11 +359,9 @@ GPUCommon::GPUCommon(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : // The compiler was not rounding the struct size up to an 8 byte boundary, which // you'd expect due to the int64 field, but the Linux ABI apparently does not require that. static_assert(sizeof(DisplayList) == 456, "Bad DisplayList size"); - listLock.set_enabled(g_Config.bSeparateCPUThread); Reinitialize(); SetupColorConv(); - SetThreadEnabled(g_Config.bSeparateCPUThread); gstate.Reset(); gstate_c.Reset(); gpuStats.Reset(); @@ -383,16 +381,7 @@ void GPUCommon::EndHostFrame() { } -void GPUCommon::InitClear() { - ScheduleEvent(GPU_EVENT_INIT_CLEAR); -} - -void GPUCommon::CopyDisplayToOutput() { - ScheduleEvent(GPU_EVENT_COPY_DISPLAY_TO_OUTPUT); -} - void GPUCommon::Reinitialize() { - easy_guard guard(listLock); memset(dls, 0, sizeof(dls)); for (int i = 0; i < DisplayListMaxCount; ++i) { dls[i].state = PSP_GE_DL_STATE_NONE; @@ -400,14 +389,12 @@ void GPUCommon::Reinitialize() { } nextListID = 0; - currentList = NULL; + currentList = nullptr; isbreak = false; drawCompleteTicks = 0; busyTicks = 0; timeSpentStepping_ = 0.0; interruptsEnabled_ = true; - UpdateTickEstimate(0); - ScheduleEvent(GPU_EVENT_REINITIALIZE); } int GPUCommon::EstimatePerVertexCost() { @@ -440,7 +427,6 @@ int GPUCommon::EstimatePerVertexCost() { } void GPUCommon::PopDLQueue() { - easy_guard guard(listLock); if(!dlQueue.empty()) { dlQueue.pop_front(); if(!dlQueue.empty()) { @@ -449,7 +435,7 @@ void GPUCommon::PopDLQueue() { if (running) currentList->state = PSP_GE_DL_STATE_RUNNING; } else { - currentList = NULL; + currentList = nullptr; } } } @@ -457,7 +443,6 @@ void GPUCommon::PopDLQueue() { bool GPUCommon::BusyDrawing() { u32 state = DrawSync(1); if (state == PSP_GE_LIST_DRAWING || state == PSP_GE_LIST_STALLING) { - easy_guard guard(listLock); if (currentList && currentList->state != PSP_GE_DL_STATE_PAUSED) { return true; } @@ -474,12 +459,6 @@ void GPUCommon::DumpNextFrame() { } u32 GPUCommon::DrawSync(int mode) { - if (ThreadEnabled()) { - // Sync first, because the CPU is usually faster than the emulated GPU. - SyncThread(); - } - - easy_guard guard(listLock); if (mode < 0 || mode > 1) return SCE_KERNEL_ERROR_INVALID_MODE; @@ -521,7 +500,6 @@ u32 GPUCommon::DrawSync(int mode) { } void GPUCommon::CheckDrawSync() { - easy_guard guard(listLock); if (dlQueue.empty()) { for (int i = 0; i < DisplayListMaxCount; ++i) dls[i].state = PSP_GE_DL_STATE_NONE; @@ -529,12 +507,6 @@ void GPUCommon::CheckDrawSync() { } int GPUCommon::ListSync(int listid, int mode) { - if (ThreadEnabled()) { - // Sync first, because the CPU is usually faster than the emulated GPU. - SyncThread(); - } - - easy_guard guard(listLock); if (listid < 0 || listid >= DisplayListMaxCount) return SCE_KERNEL_ERROR_INVALID_ID; @@ -579,8 +551,7 @@ int GPUCommon::ListSync(int listid, int mode) { } int GPUCommon::GetStack(int index, u32 stackPtr) { - easy_guard guard(listLock); - if (currentList == NULL) { + if (!currentList) { // Seems like it doesn't return an error code? return 0; } @@ -605,7 +576,6 @@ int GPUCommon::GetStack(int index, u32 stackPtr) { } u32 GPUCommon::EnqueueList(u32 listpc, u32 stall, int subIntrBase, PSPPointer args, bool head) { - easy_guard guard(listLock); // TODO Check the stack values in missing arg and ajust the stack depth // Check alignment @@ -704,7 +674,6 @@ u32 GPUCommon::EnqueueList(u32 listpc, u32 stall, int subIntrBase, PSPPointer= DisplayListMaxCount || dls[listid].state == PSP_GE_DL_STATE_NONE) return SCE_KERNEL_ERROR_INVALID_ID; @@ -736,7 +704,6 @@ u32 GPUCommon::DequeueList(int listid) { } u32 GPUCommon::UpdateStall(int listid, u32 newstall) { - easy_guard guard(listLock); if (listid < 0 || listid >= DisplayListMaxCount || dls[listid].state == PSP_GE_DL_STATE_NONE) return SCE_KERNEL_ERROR_INVALID_ID; auto &dl = dls[listid]; @@ -748,14 +715,12 @@ u32 GPUCommon::UpdateStall(int listid, u32 newstall) { if (dl.signal == PSP_GE_SIGNAL_HANDLER_PAUSE && PSP_CoreParameter().compat.flags().HackFixHangs) dl.signal = PSP_GE_SIGNAL_HANDLER_SUSPEND; - guard.unlock(); ProcessDLQueue(); return 0; } u32 GPUCommon::Continue() { - easy_guard guard(listLock); if (!currentList) return 0; @@ -791,13 +756,11 @@ u32 GPUCommon::Continue() { return -1; } - guard.unlock(); ProcessDLQueue(); return 0; } u32 GPUCommon::Break(int mode) { - easy_guard guard(listLock); if (mode < 0 || mode > 1) return SCE_KERNEL_ERROR_INVALID_MODE; @@ -886,8 +849,6 @@ bool GPUCommon::InterpretList(DisplayList &list) { start = time_now_d(); } - easy_guard guard(listLock); - if (list.state == PSP_GE_DL_STATE_PAUSED) return false; currentList = &list; @@ -911,14 +872,12 @@ bool GPUCommon::InterpretList(DisplayList &list) { list.interrupted = false; gpuState = list.pc == list.stall ? GPUSTATE_STALL : GPUSTATE_RUNNING; - guard.unlock(); debugRecording_ = GPURecord::IsActive(); const bool useDebugger = host->GPUDebuggingActive() || debugRecording_; const bool useFastRunLoop = !dumpThisFrame_ && !useDebugger; while (gpuState == GPUSTATE_RUNNING) { { - easy_guard innerGuard(listLock); if (list.pc == list.stall) { gpuState = GPUSTATE_STALL; if (PSP_CoreParameter().compat.flags().FlushAtStall) { @@ -935,7 +894,6 @@ bool GPUCommon::InterpretList(DisplayList &list) { } { - easy_guard innerGuard(listLock); downcount = list.stall == 0 ? 0x0FFFFFFF : (list.stall - list.pc) / 4; if (gpuState == GPUSTATE_STALL && list.stall != list.pc) { @@ -965,10 +923,6 @@ bool GPUCommon::InterpretList(DisplayList &list) { } void GPUCommon::BeginFrame() { - ScheduleEvent(GPU_EVENT_BEGIN_FRAME); -} - -void GPUCommon::BeginFrameInternal() { if (dumpNextFrame_) { NOTICE_LOG(G3D, "DUMPING THIS FRAME"); dumpThisFrame_ = true; @@ -1032,14 +986,6 @@ void GPUCommon::UpdatePC(u32 currentPC, u32 newPC) { } void GPUCommon::ReapplyGfxState() { - if (IsOnSeparateCPUThread()) { - ScheduleEvent(GPU_EVENT_REAPPLY_GFX_STATE); - } else { - ReapplyGfxStateInternal(); - } -} - -void GPUCommon::ReapplyGfxStateInternal() { // The commands are embedded in the command memory so we can just reexecute the words. Convenient. // To be safe we pass 0xFFFFFFFF as the diff. @@ -1070,55 +1016,7 @@ inline void GPUCommon::UpdateState(GPURunState state) { downcount = 0; } -void GPUCommon::ProcessEvent(GPUEvent ev) { - switch (ev.type) { - case GPU_EVENT_PROCESS_QUEUE: - ProcessDLQueueInternal(); - break; - - case GPU_EVENT_REAPPLY_GFX_STATE: - ReapplyGfxStateInternal(); - break; - - case GPU_EVENT_INIT_CLEAR: - InitClearInternal(); - break; - - case GPU_EVENT_BEGIN_FRAME: - BeginFrameInternal(); - break; - - case GPU_EVENT_COPY_DISPLAY_TO_OUTPUT: - CopyDisplayToOutputInternal(); - break; - - case GPU_EVENT_INVALIDATE_CACHE: - InvalidateCacheInternal(ev.invalidate_cache.addr, ev.invalidate_cache.size, ev.invalidate_cache.type); - break; - - case GPU_EVENT_FB_MEMCPY: - PerformMemoryCopyInternal(ev.fb_memcpy.dst, ev.fb_memcpy.src, ev.fb_memcpy.size); - break; - - case GPU_EVENT_FB_MEMSET: - PerformMemorySetInternal(ev.fb_memset.dst, ev.fb_memset.v, ev.fb_memset.size); - break; - - case GPU_EVENT_FB_STENCIL_UPLOAD: - PerformStencilUploadInternal(ev.fb_stencil_upload.dst, ev.fb_stencil_upload.size); - break; - - case GPU_EVENT_REINITIALIZE: - break; - - default: - ERROR_LOG_REPORT(G3D, "Unexpected GPU event type: %d", (int)ev); - break; - } -} - int GPUCommon::GetNextListIndex() { - easy_guard guard(listLock); auto iter = dlQueue.begin(); if (iter != dlQueue.end()) { return *iter; @@ -1127,15 +1025,9 @@ int GPUCommon::GetNextListIndex() { } } -bool GPUCommon::ProcessDLQueue() { - ScheduleEvent(GPU_EVENT_PROCESS_QUEUE); - return true; -} - -void GPUCommon::ProcessDLQueueInternal() { +void GPUCommon::ProcessDLQueue() { startingTicks = CoreTiming::GetTicks(); cyclesExecuted = 0; - UpdateTickEstimate(std::max(busyTicks, startingTicks + cyclesExecuted)); // Seems to be correct behaviour to process the list anyway? if (startingTicks < busyTicks) { @@ -1149,25 +1041,20 @@ void GPUCommon::ProcessDLQueueInternal() { if (!InterpretList(l)) { return; } else { - easy_guard guard(listLock); - // Some other list could've taken the spot while we dilly-dallied around. if (l.state != PSP_GE_DL_STATE_QUEUED) { // At the end, we can remove it from the queue and continue. dlQueue.erase(std::remove(dlQueue.begin(), dlQueue.end(), listIndex), dlQueue.end()); } - UpdateTickEstimate(std::max(busyTicks, startingTicks + cyclesExecuted)); } } - easy_guard guard(listLock); - currentList = NULL; + currentList = nullptr; drawCompleteTicks = startingTicks + cyclesExecuted; busyTicks = std::max(busyTicks, drawCompleteTicks); __GeTriggerSync(GPU_SYNC_DRAW, 1, drawCompleteTicks); // Since the event is in CoreTiming, we're in sync. Just set 0 now. - UpdateTickEstimate(0); } void GPUCommon::PreExecuteOp(u32 op, u32 diff) { @@ -1187,12 +1074,10 @@ void GPUCommon::Execute_Iaddr(u32 op, u32 diff) { } void GPUCommon::Execute_Origin(u32 op, u32 diff) { - easy_guard guard(listLock); gstate_c.offsetAddr = currentList->pc; } void GPUCommon::Execute_Jump(u32 op, u32 diff) { - easy_guard guard(listLock); const u32 target = gstate_c.getRelativeAddress(op & 0x00FFFFFC); #ifdef _DEBUG if (!Memory::IsValidAddress(target)) { @@ -1207,7 +1092,6 @@ void GPUCommon::Execute_Jump(u32 op, u32 diff) { void GPUCommon::Execute_BJump(u32 op, u32 diff) { if (!currentList->bboxResult) { // bounding box jump. - easy_guard guard(listLock); const u32 target = gstate_c.getRelativeAddress(op & 0x00FFFFFC); if (Memory::IsValidAddress(target)) { UpdatePC(currentList->pc, target - 4); @@ -1220,7 +1104,6 @@ void GPUCommon::Execute_BJump(u32 op, u32 diff) { void GPUCommon::Execute_Call(u32 op, u32 diff) { PROFILE_THIS_SCOPE("gpu_call"); - easy_guard guard(listLock); // Saint Seiya needs correct support for relative calls. const u32 retval = currentList->pc + 4; @@ -1259,7 +1142,6 @@ void GPUCommon::Execute_Call(u32 op, u32 diff) { } void GPUCommon::Execute_Ret(u32 op, u32 diff) { - easy_guard guard(listLock); if (currentList->stackptr == 0) { DEBUG_LOG_REPORT(G3D, "RET: Stack empty!"); } else { @@ -1281,7 +1163,6 @@ void GPUCommon::Execute_Ret(u32 op, u32 diff) { void GPUCommon::Execute_End(u32 op, u32 diff) { Flush(); - easy_guard guard(listLock); const u32 prev = Memory::ReadUnchecked_U32(currentList->pc - 4); UpdatePC(currentList->pc, currentList->pc); // Count in a few extra cycles on END. @@ -1425,7 +1306,7 @@ void GPUCommon::Execute_End(u32 op, u32 diff) { __GeTriggerSync(GPU_SYNC_LIST, currentList->id, currentList->waitTicks); if (currentList->started && currentList->context.IsValid()) { gstate.Restore(currentList->context); - ReapplyGfxStateInternal(); + ReapplyGfxState(); } } break; @@ -1606,7 +1487,6 @@ void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) { gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | ((op + count) & 0xF); // Skip over the loaded data, it's done now. - easy_guard innerGuard(listLock); UpdatePC(currentList->pc, currentList->pc + count * 4); currentList->pc += count * 4; } @@ -1654,7 +1534,6 @@ void GPUCommon::Execute_ViewMtxNum(u32 op, u32 diff) { gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | ((op + count) & 0xF); // Skip over the loaded data, it's done now. - easy_guard innerGuard(listLock); UpdatePC(currentList->pc, currentList->pc + count * 4); currentList->pc += count * 4; } @@ -1702,7 +1581,6 @@ void GPUCommon::Execute_ProjMtxNum(u32 op, u32 diff) { gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | ((op + count) & 0x1F); // Skip over the loaded data, it's done now. - easy_guard innerGuard(listLock); UpdatePC(currentList->pc, currentList->pc + count * 4); currentList->pc += count * 4; } @@ -1751,7 +1629,6 @@ void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) { gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | ((op + count) & 0xF); // Skip over the loaded data, it's done now. - easy_guard innerGuard(listLock); UpdatePC(currentList->pc, currentList->pc + count * 4); currentList->pc += count * 4; } @@ -1818,7 +1695,6 @@ void GPUCommon::Execute_BoneMtxNum(u32 op, u32 diff) { gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | ((op + count) & 0x7F); // Skip over the loaded data, it's done now. - easy_guard innerGuard(listLock); UpdatePC(currentList->pc, currentList->pc + count * 4); currentList->pc += count * 4; } @@ -2034,8 +1910,6 @@ struct DisplayList_v2 { }; void GPUCommon::DoState(PointerWrap &p) { - easy_guard guard(listLock); - auto s = p.Section("GPUCommon", 1, 4); if (!s) return; @@ -2114,7 +1988,6 @@ void GPUCommon::InterruptStart(int listid) { interruptRunning = true; } void GPUCommon::InterruptEnd(int listid) { - easy_guard guard(listLock); interruptRunning = false; isbreak = false; @@ -2130,13 +2003,11 @@ void GPUCommon::InterruptEnd(int listid) { __GeTriggerWait(GPU_SYNC_LIST, listid); } - guard.unlock(); ProcessDLQueue(); } // TODO: Maybe cleaner to keep this in GE and trigger the clear directly? void GPUCommon::SyncEnd(GPUSyncType waitType, int listid, bool wokeThreads) { - easy_guard guard(listLock); if (waitType == GPU_SYNC_DRAW && wokeThreads) { for (int i = 0; i < DisplayListMaxCount; ++i) { @@ -2148,7 +2019,6 @@ void GPUCommon::SyncEnd(GPUSyncType waitType, int listid, bool wokeThreads) { } bool GPUCommon::GetCurrentDisplayList(DisplayList &list) { - easy_guard guard(listLock); if (!currentList) { return false; } @@ -2159,7 +2029,6 @@ bool GPUCommon::GetCurrentDisplayList(DisplayList &list) { std::vector GPUCommon::ActiveDisplayLists() { std::vector result; - easy_guard guard(listLock); for (auto it = dlQueue.begin(), end = dlQueue.end(); it != end; ++it) { result.push_back(dls[*it]); } @@ -2173,7 +2042,6 @@ void GPUCommon::ResetListPC(int listID, u32 pc) { return; } - easy_guard guard(listLock); dls[listID].pc = pc; } @@ -2183,7 +2051,6 @@ void GPUCommon::ResetListStall(int listID, u32 stall) { return; } - easy_guard guard(listLock); dls[listID].stall = stall; } @@ -2193,7 +2060,6 @@ void GPUCommon::ResetListState(int listID, DisplayListState state) { return; } - easy_guard guard(listLock); dls[listID].state = state; } @@ -2345,38 +2211,17 @@ void GPUCommon::DoBlockTransfer(u32 skipDrawReason) { cyclesExecuted += ((height * width * bpp) * 16) / 10; } -void GPUCommon::PerformMemoryCopyInternal(u32 dest, u32 src, int size) { - if (!framebufferManager_->NotifyFramebufferCopy(src, dest, size, false, gstate_c.skipDrawReason)) { - // We use a little hack for Download/Upload using a VRAM mirror. - // Since they're identical we don't need to copy. - if (!Memory::IsVRAMAddress(dest) || (dest ^ 0x00400000) != src) { - Memory::Memcpy(dest, src, size); - } - } - InvalidateCache(dest, size, GPU_INVALIDATE_HINT); -} - -void GPUCommon::PerformMemorySetInternal(u32 dest, u8 v, int size) { - if (!framebufferManager_->NotifyFramebufferCopy(dest, dest, size, true, gstate_c.skipDrawReason)) { - InvalidateCache(dest, size, GPU_INVALIDATE_HINT); - } -} - bool GPUCommon::PerformMemoryCopy(u32 dest, u32 src, int size) { // Track stray copies of a framebuffer in RAM. MotoGP does this. if (framebufferManager_->MayIntersectFramebuffer(src) || framebufferManager_->MayIntersectFramebuffer(dest)) { - if (IsOnSeparateCPUThread()) { - GPUEvent ev(GPU_EVENT_FB_MEMCPY); - ev.fb_memcpy.dst = dest; - ev.fb_memcpy.src = src; - ev.fb_memcpy.size = size; - ScheduleEvent(ev); - - // This is a memcpy, so we need to wait for it to complete. - SyncThread(); - } else { - PerformMemoryCopyInternal(dest, src, size); + if (!framebufferManager_->NotifyFramebufferCopy(src, dest, size, false, gstate_c.skipDrawReason)) { + // We use a little hack for Download/Upload using a VRAM mirror. + // Since they're identical we don't need to copy. + if (!Memory::IsVRAMAddress(dest) || (dest ^ 0x00400000) != src) { + Memory::Memcpy(dest, src, size); + } } + InvalidateCache(dest, size, GPU_INVALIDATE_HINT); return true; } @@ -2389,17 +2234,8 @@ bool GPUCommon::PerformMemorySet(u32 dest, u8 v, int size) { // This may indicate a memset, usually to 0, of a framebuffer. if (framebufferManager_->MayIntersectFramebuffer(dest)) { Memory::Memset(dest, v, size); - - if (IsOnSeparateCPUThread()) { - GPUEvent ev(GPU_EVENT_FB_MEMSET); - ev.fb_memset.dst = dest; - ev.fb_memset.v = v; - ev.fb_memset.size = size; - ScheduleEvent(ev); - - // We don't need to wait for the framebuffer to be updated. - } else { - PerformMemorySetInternal(dest, v, size); + if (!framebufferManager_->NotifyFramebufferCopy(dest, dest, size, true, gstate_c.skipDrawReason)) { + InvalidateCache(dest, size, GPU_INVALIDATE_HINT); } return true; } @@ -2430,14 +2266,6 @@ bool GPUCommon::PerformMemoryUpload(u32 dest, int size) { } void GPUCommon::InvalidateCache(u32 addr, int size, GPUInvalidationType type) { - GPUEvent ev(GPU_EVENT_INVALIDATE_CACHE); - ev.invalidate_cache.addr = addr; - ev.invalidate_cache.size = size; - ev.invalidate_cache.type = type; - ScheduleEvent(ev); -} - -void GPUCommon::InvalidateCacheInternal(u32 addr, int size, GPUInvalidationType type) { if (size > 0) textureCache_->Invalidate(addr, size, type); else @@ -2462,23 +2290,12 @@ void GPUCommon::NotifyVideoUpload(u32 addr, int size, int width, int format) { bool GPUCommon::PerformStencilUpload(u32 dest, int size) { if (framebufferManager_->MayIntersectFramebuffer(dest)) { - if (IsOnSeparateCPUThread()) { - GPUEvent ev(GPU_EVENT_FB_STENCIL_UPLOAD); - ev.fb_stencil_upload.dst = dest; - ev.fb_stencil_upload.size = size; - ScheduleEvent(ev); - } else { - PerformStencilUploadInternal(dest, size); - } + framebufferManager_->NotifyStencilUpload(dest, size); return true; } return false; } -void GPUCommon::PerformStencilUploadInternal(u32 dest, int size) { - framebufferManager_->NotifyStencilUpload(dest, size); -} - bool GPUCommon::GetCurrentFramebuffer(GPUDebugBuffer &buffer, GPUDebugFramebufferType type, int maxRes) { u32 fb_address = type == GPU_DBG_FRAMEBUF_RENDER ? gstate.getFrameBufRawAddress() : framebufferManager_->DisplayFramebufAddr(); int fb_stride = type == GPU_DBG_FRAMEBUF_RENDER ? gstate.FrameBufStride() : framebufferManager_->DisplayFramebufStride(); diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 3d2430125d..d3b53ad514 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -2,7 +2,6 @@ #include "Common/Common.h" #include "Common/MemoryUtil.h" -#include "Core/ThreadEventQueue.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" #include "GPU/Common/GPUDebugInterface.h" @@ -15,8 +14,6 @@ #include #endif -typedef ThreadEventQueue GPUThreadEventQueue; - class FramebufferManagerCommon; class TextureCacheCommon; class DrawEngineCommon; @@ -42,7 +39,7 @@ enum { FLAG_DIRTYONCHANGE = 64, // NOTE: Either this or FLAG_EXECUTE*, not both! }; -class GPUCommon : public GPUThreadEventQueue, public GPUDebugInterface { +class GPUCommon : public GPUInterface, public GPUDebugInterface { public: GPUCommon(GraphicsContext *gfxCtx, Draw::DrawContext *draw); virtual ~GPUCommon(); @@ -69,7 +66,7 @@ public: void PreExecuteOp(u32 op, u32 diff) override; bool InterpretList(DisplayList &list) override; - virtual bool ProcessDLQueue(); + void ProcessDLQueue(); u32 UpdateStall(int listid, u32 newstall) override; u32 EnqueueList(u32 listpc, u32 stall, int subIntrBase, PSPPointer args, bool head) override; u32 DequeueList(int listid) override; @@ -77,21 +74,13 @@ public: u32 DrawSync(int mode) override; int GetStack(int index, u32 stackPtr) override; void DoState(PointerWrap &p) override; - bool FramebufferDirty() override { - SyncThread(); - return true; - } - bool FramebufferReallyDirty() override { - SyncThread(); - return true; - } bool BusyDrawing() override; u32 Continue() override; u32 Break(int mode) override; void ReapplyGfxState() override; - void CopyDisplayToOutput() override; - void InitClear() override; + void CopyDisplayToOutput() override = 0; + void InitClear() override = 0; bool PerformMemoryCopy(u32 dest, u32 src, int size) override; bool PerformMemorySet(u32 dest, u8 v, int size) override; bool PerformMemoryDownload(u32 dest, int size) override; @@ -116,10 +105,6 @@ public: void Execute_BoundingBox(u32 op, u32 diff); void Execute_BlockTransferStart(u32 op, u32 diff); - void Execute_TexScaleU(u32 op, u32 diff); - void Execute_TexScaleV(u32 op, u32 diff); - void Execute_TexOffsetU(u32 op, u32 diff); - void Execute_TexOffsetV(u32 op, u32 diff); void Execute_TexLevel(u32 op, u32 diff); void Execute_WorldMtxNum(u32 op, u32 diff); @@ -142,20 +127,6 @@ public: // Note: Not virtual! inline void Flush(); - u64 GetTickEstimate() override { -#if defined(_M_X64) || defined(__ANDROID__) - return curTickEst_; -#elif defined(_M_SSE) - __m64 result = *(__m64 *)&curTickEst_; - u64 safeResult = *(u64 *)&result; - _mm_empty(); - return safeResult; -#else - std::lock_guard guard(curTickEstLock_); - return curTickEst_; -#endif - } - #ifdef USE_CRT_DBG #undef new #endif @@ -254,11 +225,7 @@ protected: } } - virtual void InitClearInternal() {} void BeginFrame() override; - virtual void BeginFrameInternal(); - virtual void CopyDisplayToOutputInternal() {} - virtual void ReinitializeInternal() {} // To avoid virtual calls to PreExecuteOp(). virtual void FastRunLoop(DisplayList &list) = 0; @@ -268,15 +235,10 @@ protected: void PopDLQueue(); void CheckDrawSync(); int GetNextListIndex(); - void ProcessDLQueueInternal(); - virtual void ReapplyGfxStateInternal(); virtual void FastLoadBoneMatrix(u32 target); - void ProcessEvent(GPUEvent ev) override; - bool ShouldExitEventLoop() override { - return coreState != CORE_RUNNING; - } - virtual void FinishDeferred() { - } + + // TODO: Unify this. + virtual void FinishDeferred() {} void DoBlockTransfer(u32 skipDrawReason); @@ -289,53 +251,6 @@ protected: } } - void PerformMemoryCopyInternal(u32 dest, u32 src, int size); - void PerformMemorySetInternal(u32 dest, u8 v, int size); - void PerformStencilUploadInternal(u32 dest, int size); - void InvalidateCacheInternal(u32 addr, int size, GPUInvalidationType type); - - // This mutex can be disabled, which is useful for single core mode. - class optional_mutex { - public: - optional_mutex() : enabled_(true) {} - void set_enabled(bool enabled) { - enabled_ = enabled; - } - void lock() { - if (enabled_) - mutex_.lock(); - } - void unlock() { - if (enabled_) - mutex_.unlock(); - } - private: - std::mutex mutex_; - bool enabled_; - }; - - - // Allows early unlocking with a guard. Do not double unlock. - class easy_guard { - public: - easy_guard(optional_mutex &mtx) : mtx_(mtx), locked_(true) { mtx_.lock(); } - ~easy_guard() { - if (locked_) - mtx_.unlock(); - } - void unlock() { - if (locked_) - mtx_.unlock(); - else - Crash(); - locked_ = false; - } - - private: - optional_mutex &mtx_; - bool locked_; - }; - FramebufferManagerCommon *framebufferManager_; TextureCacheCommon *textureCache_; DrawEngineCommon *drawEngineCommon_; @@ -350,7 +265,6 @@ protected: DisplayList dls[DisplayListMaxCount]; DisplayList *currentList; DisplayListQueue dlQueue; - optional_mutex listLock; bool interruptRunning; GPURunState gpuState; @@ -372,28 +286,6 @@ protected: GEPrimitiveType lastPrim_; private: - - // For CPU/GPU sync. -#ifdef __ANDROID__ - alignas(16) std::atomic curTickEst_; -#else - alignas(16) volatile u64 curTickEst_; - std::mutex curTickEstLock_; -#endif - - inline void UpdateTickEstimate(u64 value) { -#if defined(_M_X64) || defined(__ANDROID__) - curTickEst_ = value; -#elif defined(_M_SSE) - __m64 result = *(__m64 *)&value; - *(__m64 *)&curTickEst_ = result; - _mm_empty(); -#else - std::lock_guard guard(curTickEstLock_); - curTickEst_ = value; -#endif - } - // Debug stats. double timeSteppingStarted_; double timeSpentStepping_; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 5faf3540bb..af5c1a36df 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -155,56 +155,6 @@ enum GPUInvalidationType { GPU_INVALIDATE_SAFE, }; -enum GPUEventType { - GPU_EVENT_INVALID, - GPU_EVENT_PROCESS_QUEUE, - GPU_EVENT_INIT_CLEAR, - GPU_EVENT_BEGIN_FRAME, - GPU_EVENT_COPY_DISPLAY_TO_OUTPUT, - GPU_EVENT_REAPPLY_GFX_STATE, - GPU_EVENT_INVALIDATE_CACHE, - GPU_EVENT_FINISH_EVENT_LOOP, - GPU_EVENT_SYNC_THREAD, - GPU_EVENT_FB_MEMCPY, - GPU_EVENT_FB_MEMSET, - GPU_EVENT_FB_STENCIL_UPLOAD, - GPU_EVENT_REINITIALIZE, -}; - -struct GPUEvent { - GPUEvent(GPUEventType t) : type(t) {} - GPUEventType type; - union { - // GPU_EVENT_INVALIDATE_CACHE - struct { - u32 addr; - int size; - GPUInvalidationType type; - } invalidate_cache; - // GPU_EVENT_FB_MEMCPY - struct { - u32 dst; - u32 src; - int size; - } fb_memcpy; - // GPU_EVENT_FB_MEMSET - struct { - u32 dst; - u8 v; - int size; - } fb_memset; - // GPU_EVENT_FB_STENCIL_UPLOAD - struct { - u32 dst; - int size; - } fb_stencil_upload; - }; - - operator GPUEventType() const { - return type; - } -}; - namespace Draw { class DrawContext; } @@ -225,10 +175,6 @@ public: virtual void BeginHostFrame() = 0; virtual void EndHostFrame() = 0; - // Events - virtual void RunEventsUntil(u64 globalticks) = 0; - virtual void FinishEventLoop() = 0; - // Draw queue management virtual DisplayList* getList(int listid) = 0; // TODO: Much of this should probably be shared between the different GPU implementations. @@ -277,9 +223,6 @@ public: virtual void DeviceLost() = 0; virtual void DeviceRestore() = 0; virtual void ReapplyGfxState() = 0; - virtual void SyncThread(bool force = false) = 0; - virtual void SyncBeginFrame() = 0; - virtual u64 GetTickEstimate() = 0; virtual void DoState(PointerWrap &p) = 0; // Called by the window system if the window size changed. This will be reflected in PSPCoreParam.pixel*. @@ -289,7 +232,6 @@ public: virtual bool FramebufferDirty() = 0; virtual bool FramebufferReallyDirty() = 0; virtual bool BusyDrawing() = 0; - virtual void SetThreadEnabled(bool threadEnabled) = 0; // If any jit is being used inside the GPU. virtual bool DescribeCodePtr(const u8 *ptr, std::string &name) = 0; diff --git a/GPU/Null/NullGpu.h b/GPU/Null/NullGpu.h index ae31ce3024..59b920723c 100644 --- a/GPU/Null/NullGpu.h +++ b/GPU/Null/NullGpu.h @@ -40,6 +40,7 @@ public: bool PerformMemoryUpload(u32 dest, int size) override; bool PerformStencilUpload(u32 dest, int size) override; void ClearCacheNextFrame() override {} + bool FramebufferDirty() override { return true; } void DeviceLost() override {} void DeviceRestore() override {} diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 812c164cb2..0b7a40012f 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -28,6 +28,7 @@ #include "Core/HLE/sceGe.h" #include "Core/MIPS/MIPS.h" #include "Core/Reporting.h" +#include "Core/Core.h" #include "profiler/profiler.h" #include "thin3d/thin3d.h" @@ -280,13 +281,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight) { draw_->BindIndexBuffer(nullptr, 0); } -void SoftGPU::CopyDisplayToOutput() -{ - ScheduleEvent(GPU_EVENT_COPY_DISPLAY_TO_OUTPUT); -} - -void SoftGPU::CopyDisplayToOutputInternal() -{ +void SoftGPU::CopyDisplayToOutput() { // The display always shows 480x272. CopyToCurrentFboFromDisplayRam(FB_WIDTH, FB_HEIGHT); framebufferDirty_ = false; @@ -301,17 +296,6 @@ void SoftGPU::CopyDisplayToOutputInternal() } } -void SoftGPU::ProcessEvent(GPUEvent ev) { - switch (ev.type) { - case GPU_EVENT_COPY_DISPLAY_TO_OUTPUT: - CopyDisplayToOutputInternal(); - break; - - default: - GPUCommon::ProcessEvent(ev); - } -} - void SoftGPU::FastRunLoop(DisplayList &list) { PROFILE_THIS_SCOPE("soft_runloop"); for (; downcount > 0; --downcount) { @@ -913,11 +897,6 @@ bool SoftGPU::PerformStencilUpload(u32 dest, int size) } bool SoftGPU::FramebufferDirty() { - if (g_Config.bSeparateCPUThread) { - // Allow it to process fully before deciding if it's dirty. - SyncThread(); - } - if (g_Config.iFrameSkip != 0) { bool dirty = framebufferDirty_; framebufferDirty_ = false; diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 25c21da0ec..7d5750b043 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -94,12 +94,9 @@ public: protected: void FastRunLoop(DisplayList &list) override; - void ProcessEvent(GPUEvent ev) override; void CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight); private: - void CopyDisplayToOutputInternal() override; - bool framebufferDirty_; u32 displayFramebuf_; u32 displayStride_; diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 3fc9bd9026..580c5bef20 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -986,11 +986,11 @@ void DrawEngineVulkan::DoFlush() { Draw::NativeObject object = g_Config.iRenderingMode != 0 ? Draw::NativeObject::FRAMEBUFFER_RENDERPASS : Draw::NativeObject::BACKBUFFER_RENDERPASS; VkRenderPass renderPass = (VkRenderPass)draw_->GetNativeObject(object); VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(pipelineLayout_, renderPass, pipelineKey_, dec_, vshader, fshader, false); - renderManager->BindPipeline(pipeline->pipeline); if (!pipeline) { // Already logged, let's bail out. return; } + renderManager->BindPipeline(pipeline->pipeline); if (pipeline != lastPipeline_) { if (lastPipeline_ && !lastPipeline_->useBlendConstant && pipeline->useBlendConstant) { gstate_c.Dirty(DIRTY_BLEND_STATE); diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index cfaae884f3..659762c5bf 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -177,7 +177,9 @@ void GPU_Vulkan::CheckGPUFeatures() { features |= GPU_SUPPORTS_WIDE_LINES; } if (vulkan_->GetFeaturesEnabled().dualSrcBlend) { - features |= GPU_SUPPORTS_DUALSOURCE_BLEND; + // Work around for Intel driver bug. See issue #10074. + if (vulkan_->GetPhysicalDeviceProperties().vendorID != VULKAN_VENDOR_INTEL) + features |= GPU_SUPPORTS_DUALSOURCE_BLEND; } if (vulkan_->GetFeaturesEnabled().logicOp) { features |= GPU_SUPPORTS_LOGIC_OP; @@ -332,21 +334,17 @@ void GPU_Vulkan::BuildReportingInfo() { Reporting::UpdateConfig(); } -void GPU_Vulkan::ReinitializeInternal() { +void GPU_Vulkan::Reinitialize() { + GPUCommon::Reinitialize(); textureCacheVulkan_->Clear(true); depalShaderCache_.Clear(); framebufferManagerVulkan_->DestroyAllFBOs(); } -void GPU_Vulkan::InitClearInternal() { +void GPU_Vulkan::InitClear() { bool useNonBufferedRendering = g_Config.iRenderingMode == FB_NON_BUFFERED_MODE; if (useNonBufferedRendering) { - /* - glstate.depthWrite.set(GL_TRUE); - glstate.colorMask.set(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - */ + // TODO? } } @@ -370,11 +368,6 @@ void GPU_Vulkan::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat } bool GPU_Vulkan::FramebufferDirty() { - if (ThreadEnabled()) { - // Allow it to process fully before deciding if it's dirty. - SyncThread(); - } - VirtualFramebuffer *vfb = framebufferManager_->GetDisplayVFB(); if (vfb) { bool dirty = vfb->dirtyAfterDisplay; @@ -385,11 +378,6 @@ bool GPU_Vulkan::FramebufferDirty() { } bool GPU_Vulkan::FramebufferReallyDirty() { - if (ThreadEnabled()) { - // Allow it to process fully before deciding if it's dirty. - SyncThread(); - } - VirtualFramebuffer *vfb = framebufferManager_->GetDisplayVFB(); if (vfb) { bool dirty = vfb->reallyDirtyAfterDisplay; @@ -399,7 +387,7 @@ bool GPU_Vulkan::FramebufferReallyDirty() { return true; } -void GPU_Vulkan::CopyDisplayToOutputInternal() { +void GPU_Vulkan::CopyDisplayToOutput() { // Flush anything left over. drawEngine_.Flush(); @@ -847,6 +835,7 @@ void GPU_Vulkan::GetStats(char *buffer, size_t bufsize) { "Cached, Uncached Vertices Drawn: %i, %i\n" "FBOs active: %i\n" "Textures active: %i, decoded: %i invalidated: %i\n" + "Readbacks: %d\n" "Vertex, Fragment, Pipelines loaded: %i, %i, %i\n" "Pushbuffer space used: UBO %d, Vtx %d, Idx %d\n", gpuStats.msProcessingDisplayLists * 1000.0f, @@ -864,6 +853,7 @@ void GPU_Vulkan::GetStats(char *buffer, size_t bufsize) { (int)textureCacheVulkan_->NumLoadedTextures(), gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, + gpuStats.numReadbacks, shaderManagerVulkan_->GetNumVertexShaders(), shaderManagerVulkan_->GetNumFragmentShaders(), pipelineManager_->GetNumPipelines(), diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index 4c1d49a470..289a8463f1 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -96,9 +96,9 @@ private: } void CheckFlushOp(int cmd, u32 diff); void BuildReportingInfo(); - void InitClearInternal() override; - void CopyDisplayToOutputInternal() override; - void ReinitializeInternal() override; + void InitClear() override; + void CopyDisplayToOutput() override; + void Reinitialize() override; inline void UpdateVsyncInterval(bool force); void UpdateCmdInfo(); diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index f0822a2d45..277d7ffe0f 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -281,7 +281,7 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip VkPipeline pipeline; VkResult result = vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipe, nullptr, &pipeline); if (result != VK_SUCCESS) { - ERROR_LOG(G3D, "Failed creating graphics pipeline!"); + ERROR_LOG(G3D, "Failed creating graphics pipeline! result='%s'", VulkanResultToString(result)); return nullptr; } @@ -321,6 +321,7 @@ VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VkPipelineLayout layo VulkanPipeline *pipeline = CreateVulkanPipeline( vulkan_->GetDevice(), pipelineCache_, layout, renderPass, rasterKey, vtxDec, vs, fs, useHwTransform); + // Even if the result is nullptr, insert it so we don't try to create it repeatedly. pipelines_.Insert(key, pipeline); return pipeline; } diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index af45417675..70b8a35145 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -155,6 +155,7 @@ UI::EventReturn CwCheatScreen::OnEnableAll(UI::EventParams ¶ms) { } fs.close(); + g_Config.bReloadCheats = true; return UI::EVENT_DONE; } diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 88742ab0a7..65e1cefe9f 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1014,10 +1014,13 @@ void EmuScreen::postRender() { void EmuScreen::render() { using namespace Draw; + DrawContext *thin3d = screenManager()->getDrawContext(); + if (invalid_) { // It's possible this might be set outside PSP_RunLoopFor(). // In this case, we need to double check it here. checkPowerDown(); + thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR }); return; } @@ -1032,6 +1035,8 @@ void EmuScreen::render() { } } + Core_UpdateDebugStats(g_Config.bShowDebugStats || g_Config.bLogFrameDrops); + PSP_BeginHostFrame(); // We just run the CPU until we get to vblank. This will quickly sync up pretty nicely. @@ -1047,22 +1052,19 @@ void EmuScreen::render() { if (coreState == CORE_NEXTFRAME) { // set back to running for the next frame coreState = CORE_RUNNING; + } else { + // Didn't actually reach the end of the frame, ran out of the blockTicks cycles. + // In this case we need to bind and wipe the backbuffer, at least. + // It's possible we never ended up outputted anything - make sure we have the backbuffer cleared + thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR }); } checkPowerDown(); PSP_EndHostFrame(); - if (invalid_) return; - // Here the backbuffer will always be bound. - if (!osm.IsEmpty() || g_Config.bShowDebugStats || g_Config.iShowFPSCounter || g_Config.bShowTouchControls || g_Config.bShowDeveloperMenu || g_Config.bShowAudioDebug || saveStatePreview_->GetVisibility() != UI::V_GONE || g_Config.bShowFrameProfiler) { - DrawContext *thin3d = screenManager()->getDrawContext(); - - // It's possible we never ended up outputted anything - make sure we have the backbuffer. - thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::KEEP }); - // This sets up some important states but not the viewport. screenManager()->getUIContext()->Begin(); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 90766b1031..b1eb2e3d2d 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -670,13 +670,7 @@ void GameSettingsScreen::CreateViews() { systemSettings->Add(new CheckBox(&g_Config.bFastMemory, sy->T("Fast Memory", "Fast Memory (Unstable)")))->OnClick.Handle(this, &GameSettingsScreen::OnJitAffectingSetting); - auto separateCPUThread = new CheckBox(&g_Config.bSeparateCPUThread, sy->T("Multithreaded (experimental)")); - systemSettings->Add(separateCPUThread); - separateCPUThread->OnClick.Add([=](EventParams &e) { - if (g_Config.bSeparateCPUThread) - settingInfo_->Show(sy->T("Multithreaded Tip", "Not always faster, causes glitches/crashing"), e.v); - return UI::EVENT_CONTINUE; - }); + systemSettings->Add(new CheckBox(&g_Config.bSeparateIOThread, sy->T("I/O on thread (experimental)")))->SetEnabled(!PSP_IsInited()); static const char *ioTimingMethods[] = { "Fast (lag on slow storage)", "Host (bugs, less lag)", "Simulate UMD delays" }; View *ioTimingMethod = systemSettings->Add(new PopupMultiChoice(&g_Config.iIOTimingMethod, sy->T("IO timing method"), ioTimingMethods, 0, ARRAY_SIZE(ioTimingMethods), sy->GetName(), screenManager())); diff --git a/UWP/glslang_UWP/glslang_UWP.vcxproj b/UWP/glslang_UWP/glslang_UWP.vcxproj index 5673b2a5be..a4b35f9bcd 100644 --- a/UWP/glslang_UWP/glslang_UWP.vcxproj +++ b/UWP/glslang_UWP/glslang_UWP.vcxproj @@ -323,7 +323,6 @@ - @@ -365,9 +364,7 @@ - - @@ -381,7 +378,6 @@ - @@ -407,4 +403,4 @@ - \ No newline at end of file + diff --git a/UWP/glslang_UWP/glslang_UWP.vcxproj.filters b/UWP/glslang_UWP/glslang_UWP.vcxproj.filters index 70bbd5a997..443bf3bd7d 100644 --- a/UWP/glslang_UWP/glslang_UWP.vcxproj.filters +++ b/UWP/glslang_UWP/glslang_UWP.vcxproj.filters @@ -103,15 +103,9 @@ glslang\MachineIndependent\Preprocessor - - glslang\MachineIndependent\Preprocessor - glslang\MachineIndependent\Preprocessor - - glslang\MachineIndependent\Preprocessor - glslang\MachineIndependent\Preprocessor @@ -154,9 +148,6 @@ hlsl - - hlsl - hlsl @@ -314,9 +305,6 @@ hlsl - - hlsl - hlsl @@ -330,4 +318,4 @@ OGLCompilersDLL - \ No newline at end of file + diff --git a/android/build.gradle b/android/build.gradle index 097a02e654..9d9bb9c4d1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -30,8 +30,6 @@ android { } } else { release { - // Can't build release builds without a key store. - println 'This build type should only be invoked from CI. Use Optimized builds instead.' } } } @@ -137,5 +135,5 @@ afterEvaluate { android.sourceSets.main.assets.getSrcDirs().each { println it } } dependencies { - compile project(':com.bda.controller') + implementation project(':com.bda.controller') } diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 19a133502f..8517e5bdf1 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -290,12 +290,21 @@ bool AndroidVulkanContext::Init(ANativeWindow *wnd, int desiredBackbufferSizeX, return false; } - g_Vulkan->ChooseDevice(g_Vulkan->GetBestPhysicalDevice()); + int physicalDevice = g_Vulkan->GetBestPhysicalDevice(); + if (physicalDevice < 0) { + ELOG("No usable Vulkan device found."); + delete g_Vulkan; + g_Vulkan = nullptr; + return false; + } + + g_Vulkan->ChooseDevice(physicalDevice); // Here we can enable device extensions if we like. ILOG("Creating vulkan device"); if (g_Vulkan->CreateDevice() != VK_SUCCESS) { ILOG("Failed to create vulkan device: %s", g_Vulkan->InitError().c_str()); + System_SendMessage("toast", "No Vulkan driver found. Using OpenGL instead."); delete g_Vulkan; g_Vulkan = nullptr; return false; diff --git a/android/src/org/ppsspp/ppsspp/TextRenderer.java b/android/src/org/ppsspp/ppsspp/TextRenderer.java index 8eff130e86..587f0e3baa 100644 --- a/android/src/org/ppsspp/ppsspp/TextRenderer.java +++ b/android/src/org/ppsspp/ppsspp/TextRenderer.java @@ -57,6 +57,7 @@ public class TextRenderer { int [] pixels = new int[w * h]; bmp.getPixels(pixels, 0, w, 0, 0, w, h); + bmp.recycle(); return pixels; } } diff --git a/ext/glslang b/ext/glslang index 136b1e2d5d..2edde6665d 160000 --- a/ext/glslang +++ b/ext/glslang @@ -1 +1 @@ -Subproject commit 136b1e2d5d90284fd7bdd77ed605c70a8d31c8c4 +Subproject commit 2edde6665d9a56ead5ea0e55b4e64d9a803e6164 diff --git a/ext/glslang-build/Android.mk b/ext/glslang-build/Android.mk index f88373e782..8bf8106ca0 100644 --- a/ext/glslang-build/Android.mk +++ b/ext/glslang-build/Android.mk @@ -33,16 +33,13 @@ LOCAL_SRC_FILES := \ ../glslang/glslang/MachineIndependent/preprocessor/Pp.cpp \ ../glslang/glslang/MachineIndependent/preprocessor/PpAtom.cpp \ ../glslang/glslang/MachineIndependent/preprocessor/PpContext.cpp \ - ../glslang/glslang/MachineIndependent/preprocessor/PpMemory.cpp \ ../glslang/glslang/MachineIndependent/preprocessor/PpScanner.cpp \ - ../glslang/glslang/MachineIndependent/preprocessor/PpSymbols.cpp \ ../glslang/glslang/MachineIndependent/preprocessor/PpTokens.cpp \ ../glslang/glslang/OSDependent/Unix/ossource.cpp \ ../glslang/hlsl/hlslAttributes.cpp \ ../glslang/hlsl/hlslGrammar.cpp \ ../glslang/hlsl/hlslOpMap.cpp \ ../glslang/hlsl/hlslParseables.cpp \ - ../glslang/hlsl/hlslParseHelper.cpp \ ../glslang/hlsl/hlslScanContext.cpp \ ../glslang/hlsl/hlslTokenStream.cpp \ ../glslang/SPIRV/disassemble.cpp \ diff --git a/ext/glslang.vcxproj b/ext/glslang.vcxproj index 90cb9ce1ee..54b3581255 100644 --- a/ext/glslang.vcxproj +++ b/ext/glslang.vcxproj @@ -22,7 +22,8 @@ {EDFA2E87-8AC1-4853-95D4-D7594FF81947} Win32Proj glslang - + + @@ -168,9 +169,7 @@ - - @@ -184,7 +183,6 @@ - @@ -231,7 +229,6 @@ - diff --git a/ext/glslang.vcxproj.filters b/ext/glslang.vcxproj.filters index 3031780531..af94d50565 100644 --- a/ext/glslang.vcxproj.filters +++ b/ext/glslang.vcxproj.filters @@ -42,15 +42,9 @@ glslang - - glslang - glslang - - glslang - glslang @@ -137,9 +131,6 @@ HLSL - - HLSL - HLSL @@ -290,9 +281,6 @@ HLSL - - HLSL - HLSL diff --git a/ext/native/gfx_es2/draw_text_android.cpp b/ext/native/gfx_es2/draw_text_android.cpp index e01c9a5bb3..cb5ab910a2 100644 --- a/ext/native/gfx_es2/draw_text_android.cpp +++ b/ext/native/gfx_es2/draw_text_android.cpp @@ -164,9 +164,6 @@ void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x, std::string text(NormalizeString(std::string(str))); if (text.empty()) return; - JNIEnv *env; - int result = javaVM->GetEnv((void **)&env, JNI_VERSION_1_6); - assert(env == env_); uint32_t stringHash = hash::Adler32((const uint8_t *)text.data(), text.size()); uint32_t entryHash = stringHash ^ fontHash_ ^ (align << 24); @@ -221,6 +218,7 @@ void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x, } } env_->ReleaseIntArrayElements(imageData, jimage, 0); + env_->DeleteLocalRef(imageData); desc.initData.push_back((uint8_t *)bitmapData); entry->texture = draw_->CreateTexture(desc); delete[] bitmapData; diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index 7006735086..df43465603 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -2,6 +2,8 @@ #include "VulkanQueueRunner.h" #include "VulkanRenderManager.h" +// Debug help: adb logcat -s DEBUG NativeActivity NativeApp PPSSPP + // TODO: This is only enough for 4x render resolution / 4x texture upscale for debugger. // Maybe we should use a dynamically allocated one for larger? const uint32_t readbackBufferSize = 2048 * 2048 * 4; @@ -342,6 +344,12 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c iter.fb->Release(); } + // Don't execute empty renderpasses. + if (step.commands.empty() && step.render.color == VKRRenderPassAction::KEEP && step.render.depthStencil == VKRRenderPassAction::KEEP) { + // Nothing to do. + return; + } + // This is supposed to bind a vulkan render pass to the command buffer. PerformBindFramebufferAsRenderTarget(step, cmd); @@ -481,28 +489,17 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c } void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step, VkCommandBuffer cmd) { + VkRenderPass renderPass; + int numClearVals = 0; + VkClearValue clearVal[2]{}; VkFramebuffer framebuf; int w; int h; - VkImageLayout prevLayout; if (step.render.framebuffer) { VKRFramebuffer *fb = step.render.framebuffer; framebuf = fb->framebuf; w = fb->width; h = fb->height; - prevLayout = fb->color.layout; - } else { - framebuf = backbuffer_; - w = vulkan_->GetBackbufferWidth(); - h = vulkan_->GetBackbufferHeight(); - } - - VkRenderPass renderPass; - int numClearVals = 0; - VkClearValue clearVal[2]; - memset(clearVal, 0, sizeof(clearVal)); - if (step.render.framebuffer) { - VKRFramebuffer *fb = step.render.framebuffer; // Now, if the image needs transitioning, let's transition. // The backbuffer does not, that's handled by VulkanContext. if (step.render.framebuffer->color.layout != VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { @@ -526,15 +523,17 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step break; } + // Due to a known bug in the Mali driver, pass the level count explicitly. TransitionImageLayout2(cmd, fb->color.image, VK_IMAGE_ASPECT_COLOR_BIT, fb->color.layout, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, srcStage, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - srcAccessMask, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT); + srcAccessMask, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, 1); fb->color.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } if (fb->depth.layout != VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { VkAccessFlags srcAccessMask; VkPipelineStageFlags srcStage; + switch (fb->depth.layout) { case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: srcAccessMask = VK_ACCESS_SHADER_READ_BIT; @@ -552,10 +551,13 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step Crash(); break; } + + // Due to a known bug in the Mali driver, pass the level count explicitly (last param). TransitionImageLayout2(cmd, fb->depth.image, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, fb->depth.layout, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, srcStage, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - srcAccessMask, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT); + srcAccessMask, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, + 1); fb->depth.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } @@ -570,6 +572,9 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step numClearVals = 2; } } else { + framebuf = backbuffer_; + w = vulkan_->GetBackbufferWidth(); + h = vulkan_->GetBackbufferHeight(); renderPass = GetBackbufferRenderPass(); assert(step.render.color == VKRRenderPassAction::CLEAR || step.render.color == VKRRenderPassAction::DONT_CARE); assert(step.render.depthStencil == VKRRenderPassAction::CLEAR || step.render.depthStencil == VKRRenderPassAction::DONT_CARE); diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 773a7fa59b..03d1d3b0ce 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -12,7 +12,7 @@ #endif // TODO: Using a thread here is unfinished and does not work correctly. -const bool useThread = false; +const bool useThread = true; #ifndef UINT64_MAX #define UINT64_MAX 0xFFFFFFFFFFFFFFFFULL @@ -217,13 +217,15 @@ void VulkanRenderManager::StopThread(bool shutdown) { thread_.join(); VLOG("thread joined."); - // Resignal fences for next time around - must be done after join. + // Wait for any fences to finish and be resignaled, so we don't have sync issues. for (int i = 0; i < vulkan_->GetInflightFrames(); i++) { auto &frameData = frameData_[i]; frameData.readyForRun = false; - if (!shutdown && !frameData.readyForFence) { - vkDestroyFence(vulkan_->GetDevice(), frameData.fence, nullptr); - frameData.fence = vulkan_->CreateFence(true); + + std::unique_lock lock(frameData.push_mutex); + while (!frameData.readyForFence) { + VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (stop)", i); + frameData.push_condVar.wait(lock); } } } @@ -268,12 +270,11 @@ VulkanRenderManager::~VulkanRenderManager() { queueRunner_.DestroyDeviceObjects(); } -// TODO: Activate this code. void VulkanRenderManager::ThreadFunc() { setCurrentThreadName("RenderMan"); int threadFrame = threadInitFrame_; bool nextFrame = false; - while (run_) { + while (true) { { if (nextFrame) { threadFrame++; @@ -286,6 +287,10 @@ void VulkanRenderManager::ThreadFunc() { VLOG("PULL: Waiting for frame[%d].readyForRun", threadFrame); frameData.pull_condVar.wait(lock); } + if (!frameData.readyForRun && !run_) { + // This means we're out of frames to render and run_ is false, so bail. + break; + } VLOG("PULL: frame[%d].readyForRun = false", threadFrame); frameData.readyForRun = false; // Previously we had a quick exit here that avoided calling Run() if run_ was suddenly false, @@ -586,11 +591,17 @@ void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, _dbg_assert_msg_(G3D, srcRect.offset.x + srcRect.extent.width <= (uint32_t)src->width, "srcrect offset x + extent > width"); _dbg_assert_msg_(G3D, srcRect.offset.y + srcRect.extent.height <= (uint32_t)src->height, "srcrect offset y + extent > height"); + _dbg_assert_msg_(G3D, srcRect.extent.width > 0, "blit srcwidth == 0"); + _dbg_assert_msg_(G3D, srcRect.extent.height > 0, "blit srcheight == 0"); + _dbg_assert_msg_(G3D, dstRect.offset.x >= 0, "dstrect offset x < 0"); _dbg_assert_msg_(G3D, dstRect.offset.y >= 0, "dstrect offset y < 0"); _dbg_assert_msg_(G3D, dstRect.offset.x + dstRect.extent.width <= (uint32_t)dst->width, "dstrect offset x + extent > width"); _dbg_assert_msg_(G3D, dstRect.offset.y + dstRect.extent.height <= (uint32_t)dst->height, "dstrect offset y + extent > height"); + _dbg_assert_msg_(G3D, dstRect.extent.width > 0, "blit dstwidth == 0"); + _dbg_assert_msg_(G3D, dstRect.extent.height > 0, "blit dstheight == 0"); + VKRStep *step = new VKRStep{ VKRStepType::BLIT }; step->blit.aspectMask = aspectMask; @@ -650,7 +661,6 @@ void VulkanRenderManager::Finish() { } void VulkanRenderManager::Wipe() { - int curFrame = vulkan_->GetCurFrame(); for (auto step : steps_) { // Need to release held framebuffers. switch (step->stepType) { @@ -689,7 +699,13 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on. // Now, I wonder if we should do this early in the frame or late? Right now we do it early, which should be fine. VkResult res = vkAcquireNextImageKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), UINT64_MAX, acquireSemaphore_, (VkFence)VK_NULL_HANDLE, &frameData.curSwapchainImage); - assert(res == VK_SUCCESS); + if (res == VK_SUBOPTIMAL_KHR) { + // Hopefully the resize will happen shortly. Ignore. + } else if (res == VK_ERROR_OUT_OF_DATE_KHR) { + frameData.skipSwap = true; + } else { + assert(res == VK_SUCCESS); + } // TODO: Deal with the VK_SUBOPTIMAL_KHR and VK_ERROR_OUT_OF_DATE_KHR // return codes @@ -737,7 +753,7 @@ void VulkanRenderManager::Submit(int frame, bool triggerFence) { cmdBufs[numCmdBufs++] = frameData.mainCmd; VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; - if (triggerFence) { + if (triggerFence && !frameData.skipSwap) { submit_info.waitSemaphoreCount = 1; submit_info.pWaitSemaphores = &acquireSemaphore_; VkPipelineStageFlags waitStage[1]{ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; @@ -745,7 +761,7 @@ void VulkanRenderManager::Submit(int frame, bool triggerFence) { } submit_info.commandBufferCount = (uint32_t)numCmdBufs; submit_info.pCommandBuffers = cmdBufs; - if (triggerFence) { + if (triggerFence && !frameData.skipSwap) { submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = &renderingCompleteSemaphore_; } @@ -771,21 +787,25 @@ void VulkanRenderManager::EndSubmitFrame(int frame) { Submit(frame, true); - VkSwapchainKHR swapchain = vulkan_->GetSwapchain(); - VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; - present.swapchainCount = 1; - present.pSwapchains = &swapchain; - present.pImageIndices = &frameData.curSwapchainImage; - present.pWaitSemaphores = &renderingCompleteSemaphore_; - present.waitSemaphoreCount = 1; + if (!frameData.skipSwap) { + VkSwapchainKHR swapchain = vulkan_->GetSwapchain(); + VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; + present.swapchainCount = 1; + present.pSwapchains = &swapchain; + present.pImageIndices = &frameData.curSwapchainImage; + present.pWaitSemaphores = &renderingCompleteSemaphore_; + present.waitSemaphoreCount = 1; - VkResult res = vkQueuePresentKHR(vulkan_->GetGraphicsQueue(), &present); - // TODO: Deal with the VK_SUBOPTIMAL_WSI and VK_ERROR_OUT_OF_DATE_WSI - // return codes - if (res == VK_ERROR_OUT_OF_DATE_KHR) { - // ignore, it'll be fine. this happens sometimes during resizes, and we do make sure to recreate the swap chain. + VkResult res = vkQueuePresentKHR(vulkan_->GetGraphicsQueue(), &present); + // TODO: Deal with the VK_SUBOPTIMAL_WSI and VK_ERROR_OUT_OF_DATE_WSI + // return codes + if (res == VK_ERROR_OUT_OF_DATE_KHR) { + // ignore, it'll be fine. this happens sometimes during resizes, and we do make sure to recreate the swap chain. + } else { + assert(res == VK_SUCCESS); + } } else { - assert(res == VK_SUCCESS); + frameData.skipSwap = false; } } diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index 607beaf9ab..a9faf1ccdd 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -238,6 +238,7 @@ private: bool readyForFence = true; bool readyForRun = false; + bool skipSwap = false; VKRRunType type = VKRRunType::END; VkFence fence; diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index 55945e57ee..1925f057c3 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -429,7 +429,7 @@ public: switch (info) { case APINAME: return "Vulkan"; case VENDORSTRING: return vulkan_->GetPhysicalDeviceProperties().deviceName; - case VENDOR: return StringFromFormat("%08x", vulkan_->GetPhysicalDeviceProperties().vendorID); + case VENDOR: return VulkanVendorString(vulkan_->GetPhysicalDeviceProperties().vendorID); case DRIVER: return StringFromFormat("%08x", vulkan_->GetPhysicalDeviceProperties().driverVersion); case SHADELANGVERSION: return "N/A";; case APIVERSION: diff --git a/headless/Headless.cpp b/headless/Headless.cpp index ef3ead4188..a20e540a8f 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -158,6 +158,8 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, bool static double deadline; deadline = time_now() + timeout; + Core_UpdateDebugStats(g_Config.bShowDebugStats || g_Config.bLogFrameDrops); + PSP_BeginHostFrame(); coreState = CORE_RUNNING;