diff --git a/CMakeLists.txt b/CMakeLists.txt index b85ea12643..d8cdbee3a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -672,7 +672,7 @@ elseif(IOS) ios/AppDelegate.h ios/ViewController.mm ios/ViewController.h - ios/iOSCoreAudio.cpp + ios/iOSCoreAudio.mm ios/iOSCoreAudio.h ios/PPSSPPUIApplication.h ios/PPSSPPUIApplication.mm @@ -681,7 +681,7 @@ elseif(IOS) ios/iCade/iCadeReaderView.h ios/iCade/iCadeReaderView.m ios/iCade/iCadeState.h) - set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL") + set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation") if(EXISTS "${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks/GameController.framework") set(nativeExtraLibs ${nativeExtraLibs} "-weak_framework GameController") endif() @@ -692,6 +692,11 @@ elseif(IOS) set_source_files_properties(ios/AppDelegate.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) set_source_files_properties(ios/ViewController.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) + set_source_files_properties(ios/iOSCoreAudio.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) + set_source_files_properties(ios/PPSSPPUIApplication.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) + set_source_files_properties(ios/iCade/iCadeReaderView.m PROPERTIES COMPILE_FLAGS -fobjc-arc) + set_source_files_properties(ios/main.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) + set(TargetBin PPSSPP) elseif(USING_QT_UI) @@ -1854,7 +1859,11 @@ if (TargetBin) set_source_files_properties(${SHADER_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION "MacOS/assets/shaders") endif() + if (IOS) + add_executable(${TargetBin} MACOSX_BUNDLE ${ICON_PATH_ABS} ${NativeAssets} ${SHADER_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource} "ios/Launch Screen.storyboard") + else() add_executable(${TargetBin} MACOSX_BUNDLE ${ICON_PATH_ABS} ${NativeAssets} ${SHADER_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource}) + endif() elseif(WIN32) add_executable(${TargetBin} WIN32 ${NativeAppSource}) set_target_properties(${TargetBin} PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS") @@ -1872,6 +1881,7 @@ file(INSTALL assets/flash0 DESTINATION assets) endif() # packaging and code signing if(IOS) + set(DEPLOYMENT_TARGET 8.0) file(GLOB IOSAssets ios/assets/*.png) list(REMOVE_ITEM IOSAssets ${CMAKE_CURRENT_SOURCE_DIR}/ios/assets/Default-568h@2x.png) list(REMOVE_ITEM IOSAssets ${CMAKE_CURRENT_SOURCE_DIR}/ios/assets/Default-568h@3x.png) @@ -1882,6 +1892,7 @@ if(IOS) file(INSTALL pspautotests DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/assets) endif() set(RSRC_XIB_FILES assets/Icon@2x.png) + set(RSRC_XIB_FILES "Launch Screen.storyboard") set_source_files_properties(${RSRC_XIB_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources ) @@ -1891,11 +1902,14 @@ if(IOS) set(APP_DIR_NAME "$") endif() add_custom_command(TARGET PPSSPP POST_BUILD - COMMAND mkdir -p ${APP_DIR_NAME} - COMMAND tar -c -C ${CMAKE_CURRENT_BINARY_DIR} --exclude .DS_Store --exclude .git assets *.png | tar -x -C ${APP_DIR_NAME} + COMMAND mkdir -p \"${APP_DIR_NAME}\" + COMMAND tar -c -C ${CMAKE_CURRENT_BINARY_DIR} --exclude .DS_Store --exclude .git assets *.png | tar -x -C \"${APP_DIR_NAME}\" ) + set(MACOSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET}) set_target_properties(${TargetBin} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/ios/PPSSPP-Info.plist" + RESOURCE "ios/Launch Screen.storyboard" + XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET} XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "iPhone/iPad" XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES XCODE_ATTRIBUTE_ENABLE_BITCODE NO diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index ecff815da1..abf9747601 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -1547,6 +1547,10 @@ void ARM64XEmitter::CMP(ARM64Reg Rn, u32 imm, bool shift) { EncodeAddSubImmInst(1, true, shift, imm, Rn, Is64Bit(Rn) ? SP : WSP); } +void ARM64XEmitter::CMN(ARM64Reg Rn, u32 imm, bool shift) +{ + EncodeAddSubImmInst(0, true, shift, imm, Rn, Is64Bit(Rn) ? SP : WSP); +} // Data Processing (Immediate) void ARM64XEmitter::MOVZ(ARM64Reg Rd, u32 imm, ShiftAmount pos) @@ -2073,7 +2077,7 @@ void ARM64FloatEmitter::EmitLoadStoreImmediate(u8 size, u32 opc, IndexType type, } else { - _assert_msg_(DYNA_REC, !(imm < -256 || imm > 255), "%s immediate offset must be within range of -256 to 256!", __FUNCTION__); + _assert_msg_(DYNA_REC, !(imm < -256 || imm > 255), "%s immediate offset must be within range of -256 to 255!", __FUNCTION__); encoded_imm = (imm & 0x1FF) << 2; if (type == INDEX_POST) encoded_imm |= 1; @@ -3664,23 +3668,19 @@ void ARM64FloatEmitter::ABI_PopRegisters(uint32_t registers, uint32_t fp_registe } void ARM64XEmitter::ANDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) { - unsigned int n, imm_s, imm_r; + // It's probably okay to AND by extra bits. if (!Is64Bit(Rn)) imm &= 0xFFFFFFFF; - if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { - AND(Rd, Rn, imm_r, imm_s, n != 0); - } else { - _assert_msg_(JIT, scratch != INVALID_REG, "ANDSI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); + if (!TryANDI2R(Rd, Rn, imm)) { + _assert_msg_(JIT, scratch != INVALID_REG, "ANDI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); AND(Rd, Rn, scratch); } } void ARM64XEmitter::ORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) { - unsigned int n, imm_s, imm_r; - if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { - ORR(Rd, Rn, imm_r, imm_s, n != 0); - } else { + _assert_msg_(JIT, Is64Bit(Rn) || (imm & 0xFFFFFFFF00000000UL) == 0, "ORRI2R - more bits in imm than Rn"); + if (!TryORRI2R(Rd, Rn, imm)) { _assert_msg_(JIT, scratch != INVALID_REG, "ORRI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); ORR(Rd, Rn, scratch); @@ -3688,10 +3688,8 @@ void ARM64XEmitter::ORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) } void ARM64XEmitter::EORI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) { - unsigned int n, imm_s, imm_r; - if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { - EOR(Rd, Rn, imm_r, imm_s, n != 0); - } else { + _assert_msg_(JIT, Is64Bit(Rn) || (imm & 0xFFFFFFFF00000000UL) == 0, "EORI2R - more bits in imm than Rn"); + if (!TryEORI2R(Rd, Rn, imm)) { _assert_msg_(JIT, scratch != INVALID_REG, "EORI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); EOR(Rd, Rn, scratch); @@ -3699,9 +3697,13 @@ void ARM64XEmitter::EORI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) } void ARM64XEmitter::ANDSI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) { + if (!Is64Bit(Rn)) + imm &= 0xFFFFFFFF; unsigned int n, imm_s, imm_r; if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { ANDS(Rd, Rn, imm_r, imm_s, n != 0); + } else if (imm == 0) { + ANDS(Rd, Rn, Is64Bit(Rn) ? ZR : WZR); } else { _assert_msg_(JIT, scratch != INVALID_REG, "ANDSI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); @@ -3710,11 +3712,7 @@ void ARM64XEmitter::ANDSI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) } void ARM64XEmitter::ADDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) { - u32 val; - bool shift; - if (IsImmArithmetic(imm, &val, &shift)) { - ADD(Rd, Rn, val, shift); - } else { + if (!TryADDI2R(Rd, Rn, imm)) { _assert_msg_(JIT, scratch != INVALID_REG, "ADDI2R - failed to construct arithmetic immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); ADD(Rd, Rn, scratch); @@ -3722,11 +3720,7 @@ void ARM64XEmitter::ADDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) } void ARM64XEmitter::SUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) { - u32 val; - bool shift; - if (IsImmArithmetic(imm, &val, &shift)) { - SUB(Rd, Rn, val, shift); - } else { + if (!TrySUBI2R(Rd, Rn, imm)) { _assert_msg_(JIT, scratch != INVALID_REG, "SUBI2R - failed to construct arithmetic immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); SUB(Rd, Rn, scratch); @@ -3734,32 +3728,25 @@ void ARM64XEmitter::SUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) } void ARM64XEmitter::CMPI2R(ARM64Reg Rn, u64 imm, ARM64Reg scratch) { - u32 val; - bool shift; - if (IsImmArithmetic(imm, &val, &shift)) { - CMP(Rn, val, shift); - } else { + if (!TryCMPI2R(Rn, imm)) { _assert_msg_(JIT, scratch != INVALID_REG, "CMPI2R - failed to construct arithmetic immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); CMP(Rn, scratch); } } -bool ARM64XEmitter::TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { +bool ARM64XEmitter::TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { + s64 negated = Is64Bit(Rn) ? -(s64)imm : -(s32)(u32)imm; u32 val; bool shift; - if (IsImmArithmetic(imm, &val, &shift)) { + if (imm == 0) { + // Prefer MOV (ORR) instead of ADD for moves. + MOV(Rd, Rn); + return true; + } else if (IsImmArithmetic(imm, &val, &shift)) { ADD(Rd, Rn, val, shift); return true; - } else { - return false; - } -} - -bool ARM64XEmitter::TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { - u32 val; - bool shift; - if (IsImmArithmetic(imm, &val, &shift)) { + } else if (IsImmArithmetic((u64)negated, &val, &shift)) { SUB(Rd, Rn, val, shift); return true; } else { @@ -3767,40 +3754,80 @@ bool ARM64XEmitter::TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { } } -bool ARM64XEmitter::TryCMPI2R(ARM64Reg Rn, u32 imm) { +bool ARM64XEmitter::TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { + s64 negated = Is64Bit(Rn) ? -(s64)imm : -(s32)(u32)imm; u32 val; bool shift; - if (IsImmArithmetic(imm, &val, &shift)) { - CMP(Rn, val, shift); + if (imm == 0) { + // Prefer MOV (ORR) instead of ADD for moves. + MOV(Rd, Rn); + return true; + } else if (IsImmArithmetic(imm, &val, &shift)) { + SUB(Rd, Rn, val, shift); + return true; + } else if (IsImmArithmetic((u64)negated, &val, &shift)) { + ADD(Rd, Rn, val, shift); return true; } else { return false; } } -bool ARM64XEmitter::TryANDI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { +bool ARM64XEmitter::TryCMPI2R(ARM64Reg Rn, u64 imm) { + s64 negated = Is64Bit(Rn) ? -(s64)imm : -(s32)(u32)imm; + u32 val; + bool shift; + if (IsImmArithmetic(imm, &val, &shift)) { + CMP(Rn, val, shift); + return true; + } else if (IsImmArithmetic((u64)negated, &val, &shift)) { + CMN(Rn, val, shift); + return true; + } else { + return false; + } +} + +bool ARM64XEmitter::TryANDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { + if (!Is64Bit(Rn)) + imm &= 0xFFFFFFFF; u32 n, imm_r, imm_s; - if (IsImmLogical(imm, 32, &n, &imm_s, &imm_r)) { + if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { AND(Rd, Rn, imm_r, imm_s, n != 0); return true; - } else { - return false; - } -} -bool ARM64XEmitter::TryORRI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { - u32 n, imm_r, imm_s; - if (IsImmLogical(imm, 32, &n, &imm_s, &imm_r)) { - ORR(Rd, Rn, imm_r, imm_s, n != 0); + } else if (imm == 0) { + MOVI2R(Rd, 0); return true; } else { return false; } } -bool ARM64XEmitter::TryEORI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { +bool ARM64XEmitter::TryORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { + _assert_msg_(JIT, Is64Bit(Rn) || (imm & 0xFFFFFFFF00000000UL) == 0, "TryORRI2R - more bits in imm than Rn"); u32 n, imm_r, imm_s; - if (IsImmLogical(imm, 32, &n, &imm_s, &imm_r)) { + if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { + ORR(Rd, Rn, imm_r, imm_s, n != 0); + return true; + } else if (imm == 0) { + if (Rd != Rn) { + MOV(Rd, Rn); + } + return true; + } else { + return false; + } +} +bool ARM64XEmitter::TryEORI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { + _assert_msg_(JIT, Is64Bit(Rn) || (imm & 0xFFFFFFFF00000000UL) == 0, "TryEORI2R - more bits in imm than Rn"); + u32 n, imm_r, imm_s; + if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { EOR(Rd, Rn, imm_r, imm_s, n != 0); return true; + } else if (imm == 0) { + if (Rd != Rn) { + MOV(Rd, Rn); + } + return true; } else { return false; } @@ -3892,7 +3919,7 @@ void ARM64XEmitter::SUBSI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) if (IsImmArithmetic(imm, &val, &shift)) { SUBS(Rd, Rn, val, shift); } else { - _assert_msg_(JIT, scratch != INVALID_REG, "ANDSI2R - failed to construct immediate value from %08x, need scratch", (u32)imm); + _assert_msg_(JIT, scratch != INVALID_REG, "SUBSI2R - failed to construct immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); SUBS(Rd, Rn, scratch); } @@ -3906,7 +3933,6 @@ void ARM64CodeBlock::PoisonMemory(int offset) { // AArch64: 0xD4200000 = BRK 0 while (ptr < maxptr) *ptr++ = 0xD4200000; - FlushIcacheSection((u8 *)ptr, (u8 *)maxptr); } } // namespace diff --git a/Common/Arm64Emitter.h b/Common/Arm64Emitter.h index 4a05b9564b..a6ce09b3fa 100644 --- a/Common/Arm64Emitter.h +++ b/Common/Arm64Emitter.h @@ -591,6 +591,7 @@ public: void SUB(ARM64Reg Rd, ARM64Reg Rn, u32 imm, bool shift = false); void SUBS(ARM64Reg Rd, ARM64Reg Rn, u32 imm, bool shift = false); void CMP(ARM64Reg Rn, u32 imm, bool shift = false); + void CMN(ARM64Reg Rn, u32 imm, bool shift = false); // Data Processing (Immediate) void MOVZ(ARM64Reg Rd, u32 imm, ShiftAmount pos = SHIFT_0); @@ -714,13 +715,13 @@ public: void SUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch = INVALID_REG); void SUBSI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch = INVALID_REG); - bool TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm); - bool TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm); - bool TryCMPI2R(ARM64Reg Rn, u32 imm); + bool TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm); + bool TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm); + bool TryCMPI2R(ARM64Reg Rn, u64 imm); - bool TryANDI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm); - bool TryORRI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm); - bool TryEORI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm); + bool TryANDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm); + bool TryORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm); + bool TryEORI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm); // Pseudo-instruction for convenience. PUSH pushes 16 bytes even though we only push a single register. // This is so the stack pointer is always 16-byte aligned, which is checked by hardware! diff --git a/Common/CodeBlock.h b/Common/CodeBlock.h index 03ae3f8606..a0f5dd55f4 100644 --- a/Common/CodeBlock.h +++ b/Common/CodeBlock.h @@ -68,6 +68,10 @@ public: // If not WX Exclusive, no need to call ProtectMemoryPages because we never change the protection from RWX. PoisonMemory(offset); ResetCodePtr(offset); + if (PlatformIsWXExclusive()) { + // Need to re-protect the part we didn't clear. + ProtectMemoryPages(region, offset, MEM_PROT_READ | MEM_PROT_EXEC); + } } // BeginWrite/EndWrite assume that we keep appending. diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index 41a9880826..70448a0905 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -111,7 +111,9 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { instance_extensions_enabled_.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #else #if defined(VK_USE_PLATFORM_XLIB_KHR) - instance_extensions_enabled_.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + if (IsInstanceExtensionAvailable(VK_KHR_XLIB_SURFACE_EXTENSION_NAME)) { + instance_extensions_enabled_.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + } #endif //#if defined(VK_USE_PLATFORM_XCB_KHR) // instance_extensions_enabled_.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); @@ -120,19 +122,32 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { // instance_extensions_enabled_.push_back(VK_KHR_MIR_SURFACE_EXTENSION_NAME); //#endif #if defined(VK_USE_PLATFORM_WAYLAND_KHR) - instance_extensions_enabled_.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); + if (IsInstanceExtensionAvailable(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME)) { + instance_extensions_enabled_.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); + } #endif #endif if (flags_ & VULKAN_FLAG_VALIDATE) { - for (size_t i = 0; i < ARRAY_SIZE(validationLayers); i++) { - instance_layer_names_.push_back(validationLayers[i]); - device_layer_names_.push_back(validationLayers[i]); + if (IsInstanceExtensionAvailable(VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) { + for (size_t i = 0; i < ARRAY_SIZE(validationLayers); i++) { + instance_layer_names_.push_back(validationLayers[i]); + device_layer_names_.push_back(validationLayers[i]); + } + instance_extensions_enabled_.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + } else { + ELOG("Validation layer extension not available - not enabling Vulkan validation."); + flags_ &= ~VULKAN_FLAG_VALIDATE; } - instance_extensions_enabled_.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); } - VkApplicationInfo app_info { VK_STRUCTURE_TYPE_APPLICATION_INFO }; + // Validate that all the instance extensions we ask for are actually available. + for (auto ext : instance_extensions_enabled_) { + if (!IsInstanceExtensionAvailable(ext)) + WLOG("WARNING: Does not seem that instance extension '%s' is available. Trying to proceed anyway.", ext); + } + + VkApplicationInfo app_info{ VK_STRUCTURE_TYPE_APPLICATION_INFO }; app_info.pApplicationName = info.app_name; app_info.applicationVersion = info.app_ver; app_info.pEngineName = info.app_name; @@ -140,7 +155,7 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { app_info.engineVersion = 2; app_info.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo inst_info { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; + VkInstanceCreateInfo inst_info{ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; inst_info.flags = 0; inst_info.pApplicationInfo = &app_info; inst_info.enabledLayerCount = (uint32_t)instance_layer_names_.size(); @@ -544,6 +559,8 @@ VkResult VulkanContext::CreateDevice() { } assert(found); + deviceExtensionsLookup_.DEDICATED_ALLOCATION = EnableDeviceExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + VkDeviceCreateInfo device_info { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; device_info.queueCreateInfoCount = 1; device_info.pQueueCreateInfos = &queue_info; diff --git a/Common/Vulkan/VulkanContext.h b/Common/Vulkan/VulkanContext.h index 03b9f1b21a..6b441d1ffa 100644 --- a/Common/Vulkan/VulkanContext.h +++ b/Common/Vulkan/VulkanContext.h @@ -104,6 +104,11 @@ private: std::vector callbacks_; }; +// For fast extension-enabled checks. +struct VulkanDeviceExtensions { + bool DEDICATED_ALLOCATION; +}; + // VulkanContext manages the device and swapchain, and deferred deletion of objects. class VulkanContext { public: @@ -196,6 +201,14 @@ public: const VulkanPhysicalDeviceInfo &GetDeviceInfo() const { return deviceInfo_; } const VkSurfaceCapabilitiesKHR &GetSurfaceCapabilities() const { return surfCapabilities_; } + bool IsInstanceExtensionAvailable(const char *name) const { + for (auto &iter : instance_extension_properties_) { + if (!strcmp(name, iter.extensionName)) + return true; + } + return false; + } + bool IsDeviceExtensionAvailable(const char *name) const { for (auto &iter : device_extension_properties_) { if (!strcmp(name, iter.extensionName)) @@ -225,6 +238,8 @@ public: MAX_INFLIGHT_FRAMES = 3, }; + const VulkanDeviceExtensions &DeviceExtensions() { return deviceExtensionsLookup_; } + private: // A layer can expose extensions, keep track of those extensions here. struct LayerProperties { @@ -257,6 +272,7 @@ private: std::vector device_extensions_enabled_; std::vector device_extension_properties_; + VulkanDeviceExtensions deviceExtensionsLookup_{}; std::vector physical_devices_; diff --git a/Common/Vulkan/VulkanLoader.cpp b/Common/Vulkan/VulkanLoader.cpp index c82c0eef95..72a3237f04 100644 --- a/Common/Vulkan/VulkanLoader.cpp +++ b/Common/Vulkan/VulkanLoader.cpp @@ -420,7 +420,7 @@ void VulkanLoadInstanceFunctions(VkInstance instance) { LOAD_INSTANCE_FUNC(instance, vkDestroySurfaceKHR); dyn_vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); - dyn_vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); + dyn_vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); WLOG("Vulkan instance functions loaded."); } diff --git a/Core/Config.cpp b/Core/Config.cpp index bac7222e0b..255870578d 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -55,11 +55,6 @@ static const char *logSectionName = "LogDebug"; static const char *logSectionName = "Log"; #endif - -#ifdef IOS -extern bool iosCanUseJit; -#endif - struct ConfigSetting { enum Type { TYPE_TERMINATOR, @@ -314,11 +309,8 @@ static int DefaultNumWorkers() { return cpu_info.num_cores; } -// TODO: Default to IRJit on iOS when it's done. static int DefaultCpuCore() { -#ifdef IOS - return (int)(iosCanUseJit ? CPUCore::JIT : CPUCore::INTERPRETER); -#elif defined(ARM) || defined(ARM64) || defined(_M_IX86) || defined(_M_X64) +#if defined(ARM) || defined(ARM64) || defined(_M_IX86) || defined(_M_X64) return (int)CPUCore::JIT; #else return (int)CPUCore::INTERPRETER; @@ -326,9 +318,7 @@ static int DefaultCpuCore() { } static bool DefaultCodeGen() { -#ifdef IOS - return iosCanUseJit ? true : false; -#elif defined(ARM) || defined(ARM64) || defined(_M_IX86) || defined(_M_X64) +#if defined(ARM) || defined(ARM64) || defined(_M_IX86) || defined(_M_X64) return true; #else return false; @@ -423,6 +413,7 @@ static ConfigSetting cpuSettings[] = { ConfigSetting("FastMemoryAccess", &g_Config.bFastMemory, false, true, true), ReportedConfigSetting("FuncReplacements", &g_Config.bFuncReplacements, true, true, true), ConfigSetting("HideSlowWarnings", &g_Config.bHideSlowWarnings, true, true, false), + ConfigSetting("PreloadFunctions", &g_Config.bPreloadFunctions, false, true, true), ReportedConfigSetting("CPUSpeed", &g_Config.iLockedCPUSpeed, 0, true, true), ConfigSetting(false), diff --git a/Core/Config.h b/Core/Config.h index 5515377070..5bd7b8598f 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -129,6 +129,7 @@ public: bool bForceLagSync; bool bFuncReplacements; bool bHideSlowWarnings; + bool bPreloadFunctions; bool bVulkanMultithreading; bool bSeparateSASThread; diff --git a/Core/ELF/ElfReader.cpp b/Core/ELF/ElfReader.cpp index 9494604bd6..b68e7775b8 100644 --- a/Core/ELF/ElfReader.cpp +++ b/Core/ELF/ElfReader.cpp @@ -633,6 +633,17 @@ u32 ElfReader::GetTotalSectionSizeByPrefix(const std::string &prefix) const { return total; } +std::vector ElfReader::GetCodeSections() const { + std::vector ids; + for (int i = 0; i < GetNumSections(); ++i) { + u32 flags = sections[i].sh_flags; + if ((flags & (SHF_ALLOC | SHF_EXECINSTR)) == (SHF_ALLOC | SHF_EXECINSTR)) { + ids.push_back(i); + } + } + return ids; +} + bool ElfReader::LoadSymbols() { bool hasSymbols = false; diff --git a/Core/ELF/ElfReader.h b/Core/ELF/ElfReader.h index 78680c0f42..44d33a8e8e 100644 --- a/Core/ELF/ElfReader.h +++ b/Core/ELF/ElfReader.h @@ -17,9 +17,9 @@ #pragma once +#include #include "Common/CommonTypes.h" - -#include "ElfTypes.h" +#include "Core/ELF/ElfTypes.h" enum { R_MIPS_NONE, @@ -130,6 +130,8 @@ public: u32 GetTotalDataSize() const; u32 GetTotalSectionSizeByPrefix(const std::string &prefix) const; + std::vector GetCodeSections() const; + int LoadInto(u32 vaddr, bool fromTop); bool LoadSymbols(); bool LoadRelocations(const Elf32_Rel *rels, int numRelocs); diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp index 8b63cfc401..01fdfa6c61 100644 --- a/Core/HLE/HLE.cpp +++ b/Core/HLE/HLE.cpp @@ -99,6 +99,8 @@ void HLEDoState(PointerWrap &p) if (!s) return; + // Can't be inside a syscall, reset this so errors aren't misleading. + latestSyscall = nullptr; p.Do(delayedResultEvent); CoreTiming::RestoreRegisterEvent(delayedResultEvent, "HLEDelayedResult", hleDelayResultFinish); } @@ -656,11 +658,18 @@ size_t hleFormatLogArgs(char *message, size_t sz, const char *argmask) { void hleDoLogInternal(LogTypes::LOG_TYPE t, LogTypes::LOG_LEVELS level, u64 res, const char *file, int line, const char *reportTag, char retmask, const char *reason, const char *formatted_reason) { char formatted_args[4096]; - hleFormatLogArgs(formatted_args, sizeof(formatted_args), latestSyscall->argmask); + const char *funcName = "?"; + u32 funcFlags = 0; + if (latestSyscall) { + hleFormatLogArgs(formatted_args, sizeof(formatted_args), latestSyscall->argmask); - // This acts as an override (for error returns which are usually hex.) - if (retmask == '\0') - retmask = latestSyscall->retmask; + // This acts as an override (for error returns which are usually hex.) + if (retmask == '\0') + retmask = latestSyscall->retmask; + + funcName = latestSyscall->name; + funcFlags = latestSyscall->flags; + } const char *fmt; if (retmask == 'x') { @@ -677,19 +686,19 @@ void hleDoLogInternal(LogTypes::LOG_TYPE t, LogTypes::LOG_LEVELS level, u64 res, fmt = "%s%08llx=%s(%s)%s"; } - const char *kernelFlag = (latestSyscall->flags & HLE_KERNEL_SYSCALL) != 0 ? "K " : ""; - GenericLog(level, t, file, line, fmt, kernelFlag, res, latestSyscall->name, formatted_args, formatted_reason); + const char *kernelFlag = (funcFlags & HLE_KERNEL_SYSCALL) != 0 ? "K " : ""; + GenericLog(level, t, file, line, fmt, kernelFlag, res, funcName, formatted_args, formatted_reason); if (reportTag != nullptr) { // A blank string means always log, not just once. if (reportTag[0] == '\0' || Reporting::ShouldLogOnce(reportTag)) { // Here we want the original key, so that different args, etc. group together. - std::string key = std::string(kernelFlag) + std::string("%08x=") + latestSyscall->name + "(%s)"; + std::string key = std::string(kernelFlag) + std::string("%08x=") + funcName + "(%s)"; if (reason != nullptr) key += std::string(": ") + reason; char formatted_message[8192]; - snprintf(formatted_message, sizeof(formatted_message), fmt, kernelFlag, res, latestSyscall->name, formatted_args, formatted_reason); + snprintf(formatted_message, sizeof(formatted_message), fmt, kernelFlag, res, funcName, formatted_args, formatted_reason); Reporting::ReportMessageFormatted(key.c_str(), formatted_message); } } diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp index 5e1891a301..8dd52b2dcd 100644 --- a/Core/HLE/ReplaceTables.cpp +++ b/Core/HLE/ReplaceTables.cpp @@ -41,6 +41,14 @@ #include #endif +enum class GPUReplacementSkip { + MEMSET = 1, + MEMCPY = 2, + MEMMOVE = 4, +}; + +static int skipGPUReplacements = 0; + // I think these have to be pretty accurate as these are libc replacements, // but we can probably get away with approximating the VFPU vsin/vcos and vrot // pretty roughly. @@ -119,8 +127,10 @@ static int Replace_memcpy() { // Some games use memcpy on executable code. We need to flush emuhack ops. currentMIPS->InvalidateICache(srcPtr, bytes); - if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { - skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) { + if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { + skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + } } if (!skip && bytes != 0) { u8 *dst = Memory::GetPointer(destPtr); @@ -159,8 +169,10 @@ static int Replace_memcpy_jak() { return 5; } currentMIPS->InvalidateICache(srcPtr, bytes); - if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { - skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) { + if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { + skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + } } if (!skip && bytes != 0) { u8 *dst = Memory::GetPointer(destPtr); @@ -197,8 +209,10 @@ static int Replace_memcpy16() { // Some games use memcpy on executable code. We need to flush emuhack ops. currentMIPS->InvalidateICache(srcPtr, bytes); - if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { - skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) { + if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { + skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + } } if (!skip && bytes != 0) { u8 *dst = Memory::GetPointer(destPtr); @@ -220,8 +234,10 @@ static int Replace_memcpy_swizzled() { u32 srcPtr = PARAM(1); u32 pitch = PARAM(2); u32 h = PARAM(4); - if (Memory::IsVRAMAddress(srcPtr)) { - gpu->PerformMemoryDownload(srcPtr, pitch * h); + if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) { + if (Memory::IsVRAMAddress(srcPtr)) { + gpu->PerformMemoryDownload(srcPtr, pitch * h); + } } u8 *dstp = Memory::GetPointer(destPtr); const u8 *srcp = Memory::GetPointer(srcPtr); @@ -258,9 +274,11 @@ static int Replace_memmove() { bool skip = false; // Some games use memcpy on executable code. We need to flush emuhack ops. - currentMIPS->InvalidateICache(srcPtr, bytes); - if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { - skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMMOVE) == 0) { + currentMIPS->InvalidateICache(srcPtr, bytes); + if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { + skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + } } if (!skip && bytes != 0) { u8 *dst = Memory::GetPointer(destPtr); @@ -282,7 +300,7 @@ static int Replace_memset() { u8 value = PARAM(1); u32 bytes = PARAM(2); bool skip = false; - if (Memory::IsVRAMAddress(destPtr)) { + if (Memory::IsVRAMAddress(destPtr) && (skipGPUReplacements & (int)GPUReplacementSkip::MEMSET) == 0) { skip = gpu->PerformMemorySet(destPtr, value, bytes); } if (!skip && bytes != 0) { @@ -309,7 +327,7 @@ static int Replace_memset_jak() { } bool skip = false; - if (Memory::IsVRAMAddress(destPtr)) { + if (Memory::IsVRAMAddress(destPtr) && (skipGPUReplacements & (int)GPUReplacementSkip::MEMSET) == 0) { skip = gpu->PerformMemorySet(destPtr, value, bytes); } if (!skip && bytes != 0) { @@ -599,6 +617,17 @@ static bool GetMIPSStaticAddress(u32 &addr, s32 lui_offset, s32 lw_offset) { return true; } +static bool GetMIPSGPAddress(u32 &addr, s32 offset) { + const MIPSOpcode loadOp = Memory::Read_Instruction(currentMIPS->pc + offset, true); + if (MIPS_GET_RS(loadOp) == MIPS_REG_GP) { + s16 gpoff = (s16)(u16)(loadOp & 0x0000FFFF); + addr = currentMIPS->r[MIPS_REG_GP] + gpoff; + return true; + } + + return false; +} + static int Hook_godseaterburst_blit_texture() { u32 texaddr; // Only if there's no texture. @@ -1032,7 +1061,6 @@ static int Hook_tonyhawkp8_upload_tutorial_frame() { const u32 fb_address = currentMIPS->r[MIPS_REG_A0]; if (Memory::IsVRAMAddress(fb_address)) { gpu->PerformMemoryUpload(fb_address, 0x00088000); - CBreakPoints::ExecMemCheck(fb_address, true, 0x00088000, currentMIPS->pc); } return 0; } @@ -1145,7 +1173,6 @@ static int Hook_mytranwars_upload_frame() { u32 fb_address = currentMIPS->r[MIPS_REG_S0]; if (Memory::IsVRAMAddress(fb_address)) { gpu->PerformMemoryUpload(fb_address, 0x00088000); - CBreakPoints::ExecMemCheck(fb_address, true, 0x00088000, currentMIPS->pc); } return 0; } @@ -1183,6 +1210,29 @@ static int Hook_marvelalliance1_copy_after() { return 0; } +static int Hook_starocean_clear_framebuf_before() { + skipGPUReplacements |= (int)GPUReplacementSkip::MEMSET; + return 0; +} + +static int Hook_starocean_clear_framebuf_after() { + skipGPUReplacements &= ~(int)GPUReplacementSkip::MEMSET; + + // This hook runs after the copy, this is the final memcpy destination. + u32 framebuf = currentMIPS->r[MIPS_REG_V0] - 512 * 4 * 271; + u32 y_address, h_address; + + if (GetMIPSGPAddress(y_address, -204) && GetMIPSGPAddress(h_address, -200)) { + int y = (s16)Memory::Read_U16(y_address); + int h = (s16)Memory::Read_U16(h_address); + + DEBUG_LOG(HLE, "starocean_clear_framebuf() - %08x y=%d-%d", framebuf, y, h); + // TODO: This is always clearing to 0, actually, which could be faster than an upload. + gpu->PerformMemoryUpload(framebuf + 512 * y * 4, 512 * h * 4); + } + return 0; +} + #define JITFUNC(f) (&MIPSComp::MIPSFrontendInterface::f) // Can either replace with C functions or functions emitted in Asm/ArmAsm. @@ -1292,6 +1342,8 @@ static const ReplacementTableEntry entries[] = { { "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x638 }, { "marvelalliance1_copy", &Hook_marvelalliance1_copy_a1_before, 0, REPFLAG_HOOKENTER, 0x664 }, { "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x69c }, + { "starocean_clear_framebuf", &Hook_starocean_clear_framebuf_before, 0, REPFLAG_HOOKENTER, 0 }, + { "starocean_clear_framebuf", &Hook_starocean_clear_framebuf_after, 0, REPFLAG_HOOKEXIT, 0 }, {} }; @@ -1306,6 +1358,8 @@ void Replacement_Init() { continue; replacementNameLookup[entry->name].push_back(i); } + + skipGPUReplacements = 0; } void Replacement_Shutdown() { @@ -1313,8 +1367,6 @@ void Replacement_Shutdown() { replacementNameLookup.clear(); } -// TODO: Do something on load state? - int GetNumReplacementFuncs() { return ARRAY_SIZE(entries); } diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index d26eabf4f4..47a9ae7d84 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -712,6 +712,7 @@ void ImportFuncSymbol(const FuncSymbolImport &func, bool reimporting) { } WriteSyscall(func.moduleName, func.nid, func.stubAddr); currentMIPS->InvalidateICache(func.stubAddr, 8); + MIPSAnalyst::PrecompileFunction(func.stubAddr, 8); return; } @@ -730,6 +731,7 @@ void ImportFuncSymbol(const FuncSymbolImport &func, bool reimporting) { } WriteFuncStub(func.stubAddr, it->symAddr); currentMIPS->InvalidateICache(func.stubAddr, 8); + MIPSAnalyst::PrecompileFunction(func.stubAddr, 8); return; } } @@ -768,6 +770,7 @@ void ExportFuncSymbol(const FuncSymbolExport &func) { INFO_LOG(LOADER, "Resolving function %s/%08x", func.moduleName, func.nid); WriteFuncStub(it->stubAddr, func.symAddr); currentMIPS->InvalidateICache(it->stubAddr, 8); + MIPSAnalyst::PrecompileFunction(it->stubAddr, 8); } } } @@ -1234,22 +1237,10 @@ static Module *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAdd if (textSection != -1) { module->textStart = reader.GetSectionAddr(textSection); u32 textSize = reader.GetSectionSize(textSection); - module->textEnd = module->textStart + textSize; + module->textEnd = module->textStart + textSize - 4; module->nm.text_addr = module->textStart; module->nm.text_size = reader.GetTotalTextSize(); - - if (!module->isFake) { -#if !defined(MOBILE_DEVICE) - bool gotSymbols = reader.LoadSymbols(); - MIPSAnalyst::ScanForFunctions(module->textStart, module->textEnd, !gotSymbols); -#else - if (g_Config.bFuncReplacements) { - bool gotSymbols = reader.LoadSymbols(); - MIPSAnalyst::ScanForFunctions(module->textStart, module->textEnd, !gotSymbols); - } -#endif - } } else { module->nm.text_addr = 0; module->nm.text_size = 0; @@ -1269,17 +1260,44 @@ static Module *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAdd if (textSection == -1) { module->textStart = reader.GetVaddr(); module->textEnd = firstImportStubAddr - 4; + } - if (!module->isFake) { -#if !defined(MOBILE_DEVICE) - bool gotSymbols = reader.LoadSymbols(); - MIPSAnalyst::ScanForFunctions(module->textStart, module->textEnd, !gotSymbols); -#else - if (g_Config.bFuncReplacements) { - bool gotSymbols = reader.LoadSymbols(); - MIPSAnalyst::ScanForFunctions(module->textStart, module->textEnd, !gotSymbols); - } + if (!module->isFake) { + bool scan = true; +#if defined(MOBILE_DEVICE) + scan = g_Config.bFuncReplacements; #endif + + bool gotSymbols = scan && reader.LoadSymbols(); + std::vector codeSections = reader.GetCodeSections(); + for (SectionID id : codeSections) { + u32 start = reader.GetSectionAddr(id); + // Note: scan end is inclusive. + u32 end = start + reader.GetSectionSize(id) - 4; + + if (start < module->textStart) + module->textStart = start; + if (end > module->textEnd) + module->textEnd = end; + + if (scan) + MIPSAnalyst::ScanForFunctions(start, end, !gotSymbols); + } + + // Some games don't have any sections at all. + if (scan && codeSections.empty()) { + u32 scanStart = module->textStart; + u32 scanEnd = module->textEnd; + // Skip the exports and imports sections, they're not code. + if (scanEnd >= std::min(modinfo->libent, modinfo->libstub)) { + MIPSAnalyst::ScanForFunctions(scanStart, std::min(modinfo->libent, modinfo->libstub) - 4, !gotSymbols); + scanStart = std::min(modinfo->libentend, modinfo->libstubend); + } + if (scanEnd >= std::max(modinfo->libent, modinfo->libstub)) { + MIPSAnalyst::ScanForFunctions(scanStart, std::max(modinfo->libent, modinfo->libstub) - 4, !gotSymbols); + scanStart = std::max(modinfo->libentend, modinfo->libstubend); + } + MIPSAnalyst::ScanForFunctions(scanStart, scanEnd, !gotSymbols); } } @@ -1435,6 +1453,9 @@ static Module *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAdd // use module_start_func instead of entry_addr if entry_addr is 0 if (module->nm.entry_addr == 0) module->nm.entry_addr = module->nm.module_start_func; + + MIPSAnalyst::PrecompileFunctions(); + } else { module->nm.entry_addr = -1; } diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 4561e24015..da6702248a 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -26,7 +26,7 @@ #include "Common/CommonTypes.h" #include "Core/HLE/HLE.h" #include "Core/HLE/HLETables.h" -#include "Core/MIPS/MIPSInt.h" +#include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSCodeUtils.h" #include "Core/MIPS/MIPS.h" #include "Core/CoreTiming.h" @@ -888,6 +888,7 @@ static void __KernelWriteFakeSysCall(u32 nid, u32 *ptr, u32 &pos) *ptr = pos; pos += 8; WriteSyscall("FakeSysCalls", nid, *ptr); + MIPSAnalyst::PrecompileFunction(*ptr, 8); } void __KernelThreadingInit() diff --git a/Core/MIPS/ARM/ArmJit.cpp b/Core/MIPS/ARM/ArmJit.cpp index 0a7c3ad538..050cef8025 100644 --- a/Core/MIPS/ARM/ArmJit.cpp +++ b/Core/MIPS/ARM/ArmJit.cpp @@ -218,7 +218,7 @@ void ArmJit::Compile(u32 em_address) { // Drat. The VFPU hit an uneaten prefix at the end of a block. if (js.startDefaultPrefix && js.MayHavePrefix()) { - WARN_LOG(JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4); + WARN_LOG_REPORT(JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4); js.LogPrefix(); // Let's try that one more time. We won't get back here because we toggled the value. diff --git a/Core/MIPS/ARM/ArmJit.h b/Core/MIPS/ARM/ArmJit.h index 77b4c9ea51..903f43e094 100644 --- a/Core/MIPS/ARM/ArmJit.h +++ b/Core/MIPS/ARM/ArmJit.h @@ -168,6 +168,7 @@ public: int Replace_fabsf() override; JitBlockCache *GetBlockCache() override { return &blocks; } + JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks; } std::vector SaveAndClearEmuHackOps() override { return blocks.SaveAndClearEmuHackOps(); } void RestoreSavedEmuHackOps(std::vector saved) override { blocks.RestoreSavedEmuHackOps(saved); } diff --git a/Core/MIPS/ARM64/Arm64CompALU.cpp b/Core/MIPS/ARM64/Arm64CompALU.cpp index 094686050e..48572f3d96 100644 --- a/Core/MIPS/ARM64/Arm64CompALU.cpp +++ b/Core/MIPS/ARM64/Arm64CompALU.cpp @@ -57,7 +57,7 @@ static u32 EvalAnd(u32 a, u32 b) { return a & b; } static u32 EvalAdd(u32 a, u32 b) { return a + b; } static u32 EvalSub(u32 a, u32 b) { return a - b; } -void Arm64Jit::CompImmLogic(MIPSGPReg rs, MIPSGPReg rt, u32 uimm, void (ARM64XEmitter::*arith)(ARM64Reg dst, ARM64Reg src, ARM64Reg src2), bool (ARM64XEmitter::*tryArithI2R)(ARM64Reg dst, ARM64Reg src, u32 val), u32 (*eval)(u32 a, u32 b)) { +void Arm64Jit::CompImmLogic(MIPSGPReg rs, MIPSGPReg rt, u32 uimm, void (ARM64XEmitter::*arith)(ARM64Reg dst, ARM64Reg src, ARM64Reg src2), bool (ARM64XEmitter::*tryArithI2R)(ARM64Reg dst, ARM64Reg src, u64 val), u32 (*eval)(u32 a, u32 b)) { if (gpr.IsImm(rs)) { gpr.SetImm(rt, (*eval)(gpr.GetImm(rs), uimm)); } else { @@ -87,14 +87,10 @@ void Arm64Jit::Comp_IType(MIPSOpcode op) { case 9: // R(rt) = R(rs) + simm; break; //addiu // Special-case for small adjustments of pointerified registers. Commonly for SP but happens for others. if (rs == rt && gpr.IsMappedAsPointer(rs) && IsImmArithmetic(simm < 0 ? -simm : simm, nullptr, nullptr)) { - ARM64Reg r32 = gpr.R(rs); + ARM64Reg r32 = gpr.RPtr(rs); gpr.MarkDirty(r32); ARM64Reg r = EncodeRegTo64(r32); - if (simm > 0) { - ADDI2R(r, r, simm); - } else { - SUBI2R(r, r, -simm); - } + ADDI2R(r, r, simm); } else { if (simm >= 0) { CompImmLogic(rs, rt, simm, &ARM64XEmitter::ADD, &ARM64XEmitter::TryADDI2R, &EvalAdd); @@ -119,7 +115,7 @@ void Arm64Jit::Comp_IType(MIPSOpcode op) { break; } gpr.MapDirtyIn(rt, rs); - if (!TryCMPI2R(gpr.R(rs), simm)) { + if (!TryCMPI2R(gpr.R(rs), (u32)simm)) { gpr.SetRegImm(SCRATCH1, simm); CMP(gpr.R(rs), SCRATCH1); } @@ -196,7 +192,7 @@ void Arm64Jit::Comp_RType2(MIPSOpcode op) { } } -void Arm64Jit::CompType3(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, void (ARM64XEmitter::*arith)(ARM64Reg dst, ARM64Reg rm, ARM64Reg rn), bool (ARM64XEmitter::*tryArithI2R)(ARM64Reg dst, ARM64Reg rm, u32 val), u32(*eval)(u32 a, u32 b), bool symmetric) { +void Arm64Jit::CompType3(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, void (ARM64XEmitter::*arith)(ARM64Reg dst, ARM64Reg rm, ARM64Reg rn), bool (ARM64XEmitter::*tryArithI2R)(ARM64Reg dst, ARM64Reg rm, u64 val), u32(*eval)(u32 a, u32 b), bool symmetric) { if (gpr.IsImm(rs) && gpr.IsImm(rt)) { gpr.SetImm(rd, (*eval)(gpr.GetImm(rs), gpr.GetImm(rt))); return; @@ -262,7 +258,16 @@ void Arm64Jit::Comp_RType3(MIPSOpcode op) { case 32: //R(rd) = R(rs) + R(rt); break; //add case 33: //R(rd) = R(rs) + R(rt); break; //addu - CompType3(rd, rs, rt, &ARM64XEmitter::ADD, &ARM64XEmitter::TryADDI2R, &EvalAdd, true); + if (gpr.IsImm(rs) && gpr.GetImm(rs) == 0 && !gpr.IsImm(rt)) { + // Special case: actually a mov, avoid arithmetic. + gpr.MapDirtyIn(rd, rt); + MOV(gpr.R(rd), gpr.R(rt)); + } else if (gpr.IsImm(rt) && gpr.GetImm(rt) == 0 && !gpr.IsImm(rs)) { + gpr.MapDirtyIn(rd, rs); + MOV(gpr.R(rd), gpr.R(rs)); + } else { + CompType3(rd, rs, rt, &ARM64XEmitter::ADD, &ARM64XEmitter::TryADDI2R, &EvalAdd, true); + } break; case 34: //R(rd) = R(rs) - R(rt); break; //sub @@ -392,13 +397,11 @@ void Arm64Jit::CompShiftVar(MIPSOpcode op, Arm64Gen::ShiftType shiftType) { return; } gpr.MapDirtyInIn(rd, rs, rt); - // Not sure if ARM64 wraps like this so let's do it for it. (TODO: According to the ARM ARM, it will indeed mask for us so this is not necessary) - ANDI2R(SCRATCH1, gpr.R(rs), 0x1F, INVALID_REG); switch (shiftType) { - case ST_LSL: LSLV(gpr.R(rd), gpr.R(rt), SCRATCH1); break; - case ST_LSR: LSRV(gpr.R(rd), gpr.R(rt), SCRATCH1); break; - case ST_ASR: ASRV(gpr.R(rd), gpr.R(rt), SCRATCH1); break; - case ST_ROR: RORV(gpr.R(rd), gpr.R(rt), SCRATCH1); break; + case ST_LSL: LSLV(gpr.R(rd), gpr.R(rt), gpr.R(rs)); break; + case ST_LSR: LSRV(gpr.R(rd), gpr.R(rt), gpr.R(rs)); break; + case ST_ASR: ASRV(gpr.R(rd), gpr.R(rt), gpr.R(rs)); break; + case ST_ROR: RORV(gpr.R(rd), gpr.R(rt), gpr.R(rs)); break; } } diff --git a/Core/MIPS/ARM64/Arm64CompBranch.cpp b/Core/MIPS/ARM64/Arm64CompBranch.cpp index dc6355f709..7932283448 100644 --- a/Core/MIPS/ARM64/Arm64CompBranch.cpp +++ b/Core/MIPS/ARM64/Arm64CompBranch.cpp @@ -128,26 +128,56 @@ void Arm64Jit::BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely) // We might be able to flip the condition (EQ/NEQ are easy.) const bool canFlip = cc == CC_EQ || cc == CC_NEQ; - - // TODO ARM64: Optimize for immediates other than zero - if (rt == 0) { - gpr.MapIn(rs); - CMP(gpr.R(rs), 0); - } else { - gpr.MapInIn(rs, rt); - CMP(gpr.R(rs), gpr.R(rt)); - } + const bool rsIsZero = gpr.IsImm(rs) && gpr.GetImm(rs) == 0; + const bool rtIsZero = gpr.IsImm(rt) && gpr.GetImm(rt) == 0; Arm64Gen::FixupBranch ptr; - if (!likely) { - if (!delaySlotIsNice) - CompileDelaySlot(DELAYSLOT_SAFE_FLUSH); - else - FlushAll(); - ptr = B(cc); - } else { + if ((likely || delaySlotIsNice) && (rsIsZero || rtIsZero) && canFlip) { + // Special case, we can just use CBZ/CBNZ directly. + MIPSGPReg r = rsIsZero ? rt : rs; + gpr.MapReg(r); + // Flush should keep r in the same armreg. + ARM64Reg ar = gpr.R(r); FlushAll(); - ptr = B(cc); + if (cc == CC_EQ) { + ptr = CBZ(ar); + } else { + ptr = CBNZ(ar); + } + } else { + u32 val; + bool shift; + if (gpr.IsImm(rt) && IsImmArithmetic(gpr.GetImm(rt), &val, &shift)) { + gpr.MapReg(rs); + CMP(gpr.R(rs), val, shift); + } else if (gpr.IsImm(rt) && IsImmArithmetic((u64)(s64)-(s32)gpr.GetImm(rt), &val, &shift)) { + gpr.MapReg(rs); + CMN(gpr.R(rs), val, shift); + } else if (gpr.IsImm(rs) && IsImmArithmetic(gpr.GetImm(rs), &val, &shift) && canFlip) { + gpr.MapReg(rt); + CMP(gpr.R(rt), val, shift); + } else if (gpr.IsImm(rs) && IsImmArithmetic((u64)(s64)-(s32)gpr.GetImm(rs), &val, &shift) && canFlip) { + gpr.MapReg(rt); + CMN(gpr.R(rt), val, shift); + } else { + gpr.MapInIn(rs, rt); + CMP(gpr.R(rs), gpr.R(rt)); + } + + if (!likely) { + if (!delaySlotIsNice) + CompileDelaySlot(DELAYSLOT_SAFE_FLUSH); + else + FlushAll(); + ptr = B(cc); + } else { + FlushAll(); + ptr = B(cc); + } + } + + if (likely) { + // Only executed when taking the branch. CompileDelaySlot(DELAYSLOT_FLUSH); } @@ -234,7 +264,6 @@ void Arm64Jit::BranchRSZeroComp(MIPSOpcode op, CCFlags cc, bool andLink, bool li if (!likely && delaySlotIsNice) CompileDelaySlot(DELAYSLOT_NICE); - // TODO: Maybe we could use BZ here? gpr.MapReg(rs); CMP(gpr.R(rs), 0); @@ -324,19 +353,24 @@ void Arm64Jit::BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely) { if (!likely && delaySlotIsNice) CompileDelaySlot(DELAYSLOT_NICE); - // TODO: Maybe we could use TBZ here? gpr.MapReg(MIPS_REG_FPCOND); - TSTI2R(gpr.R(MIPS_REG_FPCOND), 1, SCRATCH1); Arm64Gen::FixupBranch ptr; - if (!likely) { - if (!delaySlotIsNice) - CompileDelaySlot(DELAYSLOT_SAFE_FLUSH); - else - FlushAll(); - ptr = B(cc); - } else { + if (likely || delaySlotIsNice) { + // FlushAll() won't actually change the reg. + ARM64Reg ar = gpr.R(MIPS_REG_FPCOND); FlushAll(); + if (cc == CC_EQ) { + ptr = TBZ(ar, 0); + } else { + ptr = TBNZ(ar, 0); + } + } else { + TSTI2R(gpr.R(MIPS_REG_FPCOND), 1, SCRATCH1); + CompileDelaySlot(DELAYSLOT_SAFE_FLUSH); ptr = B(cc); + } + + if (likely) { CompileDelaySlot(DELAYSLOT_FLUSH); } @@ -385,28 +419,26 @@ void Arm64Jit::BranchVFPUFlag(MIPSOpcode op, CCFlags cc, bool likely) { int imm3 = (op >> 18) & 7; - // TODO: Maybe could use TBZ? gpr.MapReg(MIPS_REG_VFPUCC); - TSTI2R(gpr.R(MIPS_REG_VFPUCC), 1 << imm3, SCRATCH1); - Arm64Gen::FixupBranch ptr; - js.inDelaySlot = true; - if (!likely) - { - if (!delaySlotIsNice && !delaySlotIsBranch) - CompileDelaySlot(DELAYSLOT_SAFE_FLUSH); - else - FlushAll(); - ptr = B(cc); - } - else - { + if (likely || delaySlotIsNice || delaySlotIsBranch) { + // FlushAll() won't actually change the reg. + ARM64Reg ar = gpr.R(MIPS_REG_VFPUCC); FlushAll(); + if (cc == CC_EQ) { + ptr = TBZ(ar, imm3); + } else { + ptr = TBNZ(ar, imm3); + } + } else { + TSTI2R(gpr.R(MIPS_REG_VFPUCC), 1 << imm3, SCRATCH1); + CompileDelaySlot(DELAYSLOT_SAFE_FLUSH); ptr = B(cc); - if (!delaySlotIsBranch) - CompileDelaySlot(DELAYSLOT_FLUSH); } - js.inDelaySlot = false; + + if (likely && !delaySlotIsBranch) { + CompileDelaySlot(DELAYSLOT_FLUSH); + } // Take the branch WriteExit(targetAddr, js.nextExit++); @@ -504,7 +536,7 @@ void Arm64Jit::Comp_JumpReg(MIPSOpcode op) delaySlotIsNice = false; CONDITIONAL_NICE_DELAYSLOT; - ARM64Reg destReg = OTHERTEMPREG; + ARM64Reg destReg = INVALID_REG; if (IsSyscall(delaySlotOp)) { gpr.MapReg(rs); MovToPC(gpr.R(rs)); // For syscall to be able to return. @@ -542,7 +574,9 @@ void Arm64Jit::Comp_JumpReg(MIPSOpcode op) destReg = gpr.R(rs); // Safe because FlushAll doesn't change any regs FlushAll(); } else { - // Delay slot - this case is very rare, might be able to free up R8. + // Since we can't be in a delay slot, should be safe to steal FLAGTEMPREG for a temp reg. + // It will be saved, even if a function is called. + destReg = FLAGTEMPREG; gpr.MapReg(rs); MOV(destReg, gpr.R(rs)); if (andLink) diff --git a/Core/MIPS/ARM64/Arm64CompFPU.cpp b/Core/MIPS/ARM64/Arm64CompFPU.cpp index bab68ca302..ea971de24f 100644 --- a/Core/MIPS/ARM64/Arm64CompFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompFPU.cpp @@ -102,8 +102,8 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) fpr.SpillLock(ft); fpr.MapReg(ft, MAP_NOINIT | MAP_DIRTY); if (gpr.IsImm(rs)) { - u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF; - gpr.SetRegImm(SCRATCH1_64, (uintptr_t)(Memory::base + addr)); + u32 addr = offset + gpr.GetImm(rs); + gpr.SetRegImm(SCRATCH1, addr); } else { gpr.MapReg(rs); if (g_Config.bFastMemory) { @@ -111,9 +111,8 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) } else { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); } - fp.LDR(32, INDEX_UNSIGNED, fpr.R(ft), SCRATCH1_64, 0); + fp.LDR(32, fpr.R(ft), SCRATCH1_64, ArithOption(MEMBASEREG)); for (auto skip : skips) { SetJumpTarget(skip); } @@ -130,8 +129,8 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) fpr.MapReg(ft); if (gpr.IsImm(rs)) { - u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF; - gpr.SetRegImm(SCRATCH1_64, addr + (uintptr_t)(Memory::base)); + u32 addr = offset + gpr.GetImm(rs); + gpr.SetRegImm(SCRATCH1, addr); } else { gpr.MapReg(rs); if (g_Config.bFastMemory) { @@ -139,9 +138,8 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) } else { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); } - fp.STR(32, INDEX_UNSIGNED, fpr.R(ft), SCRATCH1_64, 0); + fp.STR(32, fpr.R(ft), SCRATCH1_64, ArithOption(MEMBASEREG)); for (auto skip : skips) { SetJumpTarget(skip); } @@ -349,7 +347,8 @@ void Arm64Jit::Comp_mxc1(MIPSOpcode op) case 4: //FI(fs) = R(rt); break; //mtc1 if (gpr.IsImm(rt)) { - uint32_t ival = gpr.GetImm(rt); + // This can't be run on LO/HI. + uint32_t ival = (uint32_t)gpr.GetImm(rt); float floatval; memcpy(&floatval, &ival, sizeof(floatval)); uint8_t imm8; diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index 8ec1dbc75a..9a226a32e6 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -129,19 +129,16 @@ namespace MIPSComp { } } - DISABLE; - u32 iaddr = gpr.IsImm(rs) ? offset + gpr.GetImm(rs) : 0xFFFFFFFF; std::vector skips; if (gpr.IsImm(rs) && Memory::IsValidAddress(iaddr)) { - u32 addr = iaddr; // Need to initialize since this only loads part of the register. // But rs no longer matters (even if rs == rt) since we have the address. gpr.MapReg(rt, load ? MAP_DIRTY : 0); - gpr.SetRegImm(SCRATCH1, addr & ~3); + gpr.SetRegImm(SCRATCH1, iaddr & ~3); - u8 shift = (addr & 3) * 8; + u8 shift = (iaddr & 3) * 8; switch (o) { case 34: // lwl @@ -159,58 +156,38 @@ namespace MIPSComp { case 42: // swl LDR(SCRATCH2, MEMBASEREG, SCRATCH1); ANDI2R(SCRATCH2, SCRATCH2, 0xffffff00 << shift, INVALID_REG); - ORR(SCRATCH2, SCRATCH2, SCRATCH2, ArithOption(gpr.R(rt), ST_LSR, 24 - shift)); + ORR(SCRATCH2, SCRATCH2, gpr.R(rt), ArithOption(gpr.R(rt), ST_LSR, 24 - shift)); STR(SCRATCH2, MEMBASEREG, SCRATCH1); break; case 46: // swr LDR(SCRATCH2, MEMBASEREG, SCRATCH1); ANDI2R(SCRATCH2, SCRATCH2, 0x00ffffff >> (24 - shift), INVALID_REG); - ORR(SCRATCH2, SCRATCH2, SCRATCH2, ArithOption(gpr.R(rt), ST_LSL, shift)); + ORR(SCRATCH2, SCRATCH2, gpr.R(rt), ArithOption(gpr.R(rt), ST_LSL, shift)); STR(SCRATCH2, MEMBASEREG, SCRATCH1); break; } return; } - switch (o) { - case 34: // lwl - DISABLE; - break; - - case 38: // lwr - DISABLE; - break; - - case 42: // swl - break; - - case 46: // swr - break; - } - _dbg_assert_msg_(JIT, !gpr.IsImm(rs), "Invalid immediate address? CPU bug?"); if (load) { gpr.MapDirtyIn(rt, rs, false); } else { gpr.MapInIn(rt, rs); } + gpr.SpillLock(rt); + gpr.SpillLock(rs); + // Need to get temps before skipping safe mem. + ARM64Reg LR_SCRATCH3 = gpr.GetAndLockTempR(); + ARM64Reg LR_SCRATCH4 = o == 42 || o == 46 ? gpr.GetAndLockTempR() : INVALID_REG; - if (false && !g_Config.bFastMemory && rs != MIPS_REG_SP) { + if (!g_Config.bFastMemory && rs != MIPS_REG_SP) { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } else { SetScratch1ToEffectiveAddress(rs, offset); } - // Need temp regs. TODO: Get from the regcache? - static const ARM64Reg LR_SCRATCH3 = W9; - static const ARM64Reg LR_SCRATCH4 = W10; - if (false && load) { - PUSH(EncodeRegTo64(LR_SCRATCH3)); - } else { - PUSH2(EncodeRegTo64(LR_SCRATCH3), EncodeRegTo64(LR_SCRATCH4)); - } - // Here's our shift amount. ANDI2R(SCRATCH2, SCRATCH1, 3); LSL(SCRATCH2, SCRATCH2, 3); @@ -221,7 +198,7 @@ namespace MIPSComp { switch (o) { case 34: // lwl MOVI2R(LR_SCRATCH3, 0x00ffffff); - LDR(SCRATCH1, MEMBASEREG, SCRATCH1); + LDR(SCRATCH1, MEMBASEREG, ArithOption(SCRATCH1)); LSRV(LR_SCRATCH3, LR_SCRATCH3, SCRATCH2); AND(gpr.R(rt), gpr.R(rt), LR_SCRATCH3); NEG(SCRATCH2, SCRATCH2); @@ -232,7 +209,7 @@ namespace MIPSComp { case 38: // lwr MOVI2R(LR_SCRATCH3, 0xffffff00); - LDR(SCRATCH1, MEMBASEREG, SCRATCH1); + LDR(SCRATCH1, MEMBASEREG, ArithOption(SCRATCH1)); LSRV(SCRATCH1, SCRATCH1, SCRATCH2); NEG(SCRATCH2, SCRATCH2); ADDI2R(SCRATCH2, SCRATCH2, 24); @@ -243,19 +220,20 @@ namespace MIPSComp { case 42: // swl MOVI2R(LR_SCRATCH3, 0xffffff00); - LDR(LR_SCRATCH4, MEMBASEREG, SCRATCH1); + LDR(LR_SCRATCH4, MEMBASEREG, ArithOption(SCRATCH1)); LSLV(LR_SCRATCH3, LR_SCRATCH3, SCRATCH2); AND(LR_SCRATCH4, LR_SCRATCH4, LR_SCRATCH3); NEG(SCRATCH2, SCRATCH2); ADDI2R(SCRATCH2, SCRATCH2, 24); + LSRV(LR_SCRATCH3, gpr.R(rt), SCRATCH2); ORR(LR_SCRATCH4, LR_SCRATCH4, LR_SCRATCH3); - STR(LR_SCRATCH4, MEMBASEREG, SCRATCH1); + STR(LR_SCRATCH4, MEMBASEREG, ArithOption(SCRATCH1)); break; case 46: // swr MOVI2R(LR_SCRATCH3, 0x00ffffff); - LDR(LR_SCRATCH4, MEMBASEREG, SCRATCH1); + LDR(LR_SCRATCH4, MEMBASEREG, ArithOption(SCRATCH1)); NEG(SCRATCH2, SCRATCH2); ADDI2R(SCRATCH2, SCRATCH2, 24); LSRV(LR_SCRATCH3, LR_SCRATCH3, SCRATCH2); @@ -264,19 +242,15 @@ namespace MIPSComp { ADDI2R(SCRATCH2, SCRATCH2, 24); LSLV(LR_SCRATCH3, gpr.R(rt), SCRATCH2); ORR(LR_SCRATCH4, LR_SCRATCH4, LR_SCRATCH3); - STR(LR_SCRATCH4, MEMBASEREG, SCRATCH1); + STR(LR_SCRATCH4, MEMBASEREG, ArithOption(SCRATCH1)); break; } - if (false && load) { - POP(EncodeRegTo64(LR_SCRATCH3)); - } else { - POP2(EncodeRegTo64(LR_SCRATCH3), EncodeRegTo64(LR_SCRATCH4)); - } - for (auto skip : skips) { SetJumpTarget(skip); } + + gpr.ReleaseSpillLocksAndDiscardTemps(); } void Arm64Jit::Comp_ITypeMem(MIPSOpcode op) { @@ -294,7 +268,8 @@ namespace MIPSComp { u32 iaddr = gpr.IsImm(rs) ? offset + gpr.GetImm(rs) : 0xFFFFFFFF; std::vector skips; - ARM64Reg addrReg = SCRATCH1; + ARM64Reg targetReg = INVALID_REG; + ARM64Reg addrReg = INVALID_REG; int dataSize = 4; switch (o) { @@ -335,51 +310,66 @@ namespace MIPSComp { gpr.SpillLock(rs, rt); gpr.MapRegAsPointer(rs); - Arm64Gen::ARM64Reg ar; - if (!load && gpr.IsImm(rt) && gpr.GetImm(rt) == 0) { - // Can just store from the zero register directly. - ar = WZR; - } else { + // For a store, try to avoid mapping a reg if not needed. + targetReg = load ? INVALID_REG : gpr.TryMapTempImm(rt); + if (targetReg == INVALID_REG) { gpr.MapReg(rt, load ? MAP_NOINIT : 0); - ar = gpr.R(rt); + targetReg = gpr.R(rt); } switch (o) { - case 35: LDR(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; - case 37: LDRH(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; - case 33: LDRSH(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; - case 36: LDRB(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; - case 32: LDRSB(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; + case 35: LDR(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; + case 37: LDRH(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; + case 33: LDRSH(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; + case 36: LDRB(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; + case 32: LDRSB(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; // Store - case 43: STR(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; - case 41: STRH(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; - case 40: STRB(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; + case 43: STR(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; + case 41: STRH(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; + case 40: STRB(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; } - gpr.ReleaseSpillLocks(); + gpr.ReleaseSpillLocksAndDiscardTemps(); break; } } + if (!load && gpr.IsImm(rt) && gpr.TryMapTempImm(rt) != INVALID_REG) { + // We're storing an immediate value, let's see if we can optimize rt. + if (!gpr.IsImm(rs) || offset == 0) { + // In this case, we're always going to need rs mapped, which may flush the temp imm. + // We handle that in the cases below since targetReg is INVALID_REG. + gpr.MapIn(rs); + } + + targetReg = gpr.TryMapTempImm(rt); + } + if (gpr.IsImm(rs) && Memory::IsValidAddress(iaddr)) { - // TODO: Avoid mapping a register for the "zero" register, use R0 instead. - - // We can compute the full address at compile time. Kickass. - u32 addr = iaddr & 0x3FFFFFFF; - - if (addr == iaddr && offset == 0) { + if (offset == 0) { // It was already safe. Let's shove it into a reg and use it directly. - load ? gpr.MapDirtyIn(rt, rs) : gpr.MapInIn(rt, rs); + if (targetReg == INVALID_REG) { + load ? gpr.MapDirtyIn(rt, rs) : gpr.MapInIn(rt, rs); + targetReg = gpr.R(rt); + } addrReg = gpr.R(rs); } else { - // In this case, only map rt. rs+offset will be in R0. - gpr.MapReg(rt, load ? MAP_NOINIT : 0); - gpr.SetRegImm(SCRATCH1, addr); + // In this case, only map rt. rs+offset will be in SCRATCH1. + if (targetReg == INVALID_REG) { + gpr.MapReg(rt, load ? MAP_NOINIT : 0); + targetReg = gpr.R(rt); + } + gpr.SetRegImm(SCRATCH1, iaddr); addrReg = SCRATCH1; } } else { // This actually gets hit in micro machines! rs = ZR rt = ZR. Probably a bug. // Leaving this a debug assert for future investigation. _dbg_assert_msg_(JIT, !gpr.IsImm(rs), "Invalid immediate address? CPU bug?"); - load ? gpr.MapDirtyIn(rt, rs) : gpr.MapInIn(rt, rs); + + // If we already have a targetReg, we optimized an imm, and rs is already mapped. + if (targetReg == INVALID_REG) { + load ? gpr.MapDirtyIn(rt, rs) : gpr.MapInIn(rt, rs); + targetReg = gpr.R(rt); + } if (!g_Config.bFastMemory && rs != MIPS_REG_SP) { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); @@ -391,15 +381,15 @@ namespace MIPSComp { switch (o) { // Load - case 35: LDR(gpr.R(rt), MEMBASEREG, addrReg); break; - case 37: LDRH(gpr.R(rt), MEMBASEREG, addrReg); break; - case 33: LDRSH(gpr.R(rt), MEMBASEREG, addrReg); break; - case 36: LDRB(gpr.R(rt), MEMBASEREG, addrReg); break; - case 32: LDRSB(gpr.R(rt), MEMBASEREG, addrReg); break; + case 35: LDR(targetReg, MEMBASEREG, addrReg); break; + case 37: LDRH(targetReg, MEMBASEREG, addrReg); break; + case 33: LDRSH(targetReg, MEMBASEREG, addrReg); break; + case 36: LDRB(targetReg, MEMBASEREG, addrReg); break; + case 32: LDRSB(targetReg, MEMBASEREG, addrReg); break; // Store - case 43: STR(gpr.R(rt), MEMBASEREG, addrReg); break; - case 41: STRH(gpr.R(rt), MEMBASEREG, addrReg); break; - case 40: STRB(gpr.R(rt), MEMBASEREG, addrReg); break; + case 43: STR(targetReg, MEMBASEREG, addrReg); break; + case 41: STRH(targetReg, MEMBASEREG, addrReg); break; + case 40: STRB(targetReg, MEMBASEREG, addrReg); break; } for (auto skip : skips) { SetJumpTarget(skip); diff --git a/Core/MIPS/ARM64/Arm64CompVFPU.cpp b/Core/MIPS/ARM64/Arm64CompVFPU.cpp index 53105681a2..0277df28e7 100644 --- a/Core/MIPS/ARM64/Arm64CompVFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompVFPU.cpp @@ -222,7 +222,7 @@ namespace MIPSComp { fpr.MapRegV(vt, MAP_DIRTY | MAP_NOINIT); if (gpr.IsImm(rs)) { u32 addr = offset + gpr.GetImm(rs); - gpr.SetRegImm(SCRATCH1_64, addr + (uintptr_t)Memory::base); + gpr.SetRegImm(SCRATCH1, addr); } else { gpr.MapReg(rs); if (g_Config.bFastMemory) { @@ -230,10 +230,8 @@ namespace MIPSComp { } else { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } - // Pointerify - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); } - fp.LDR(32, INDEX_UNSIGNED, fpr.V(vt), SCRATCH1_64, 0); + fp.LDR(32, fpr.V(vt), SCRATCH1_64, ArithOption(MEMBASEREG)); for (auto skip : skips) { SetJumpTarget(skip); } @@ -253,7 +251,7 @@ namespace MIPSComp { fpr.MapRegV(vt); if (gpr.IsImm(rs)) { u32 addr = offset + gpr.GetImm(rs); - gpr.SetRegImm(SCRATCH1_64, addr + (uintptr_t)Memory::base); + gpr.SetRegImm(SCRATCH1, addr); } else { gpr.MapReg(rs); if (g_Config.bFastMemory) { @@ -261,9 +259,8 @@ namespace MIPSComp { } else { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); } - fp.STR(32, INDEX_UNSIGNED, fpr.V(vt), SCRATCH1_64, 0); + fp.STR(32, fpr.V(vt), SCRATCH1_64, ArithOption(MEMBASEREG)); for (auto skip : skips) { SetJumpTarget(skip); } @@ -303,7 +300,11 @@ namespace MIPSComp { } else { skips = SetScratch1ForSafeAddress(rs, imm, SCRATCH2); } - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + if (jo.enablePointerify) { + MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + } else { + ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); + } } fp.LDP(32, INDEX_SIGNED, fpr.V(vregs[0]), fpr.V(vregs[1]), SCRATCH1_64, 0); @@ -332,7 +333,11 @@ namespace MIPSComp { } else { skips = SetScratch1ForSafeAddress(rs, imm, SCRATCH2); } - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + if (jo.enablePointerify) { + MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + } else { + ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); + } } fp.STP(32, INDEX_SIGNED, fpr.V(vregs[0]), fpr.V(vregs[1]), SCRATCH1_64, 0); diff --git a/Core/MIPS/ARM64/Arm64Jit.cpp b/Core/MIPS/ARM64/Arm64Jit.cpp index ae9b6b8f4e..e1aaea334f 100644 --- a/Core/MIPS/ARM64/Arm64Jit.cpp +++ b/Core/MIPS/ARM64/Arm64Jit.cpp @@ -66,6 +66,11 @@ using namespace Arm64Gen; using namespace Arm64JitConstants; Arm64Jit::Arm64Jit(MIPSState *mips) : blocks(mips, this), gpr(mips, &js, &jo), fpr(mips, &js, &jo), mips_(mips), fp(this) { + // Automatically disable incompatible options. + if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) { + jo.enablePointerify = false; + } + logBlocks = 0; dontLogBlocks = 0; blocks.Init(); @@ -129,6 +134,7 @@ void Arm64Jit::ClearCache() { ILOG("ARM64Jit: Clearing the cache!"); blocks.Clear(); ClearCodeSpace(jitStartOffset); + FlushIcacheSection(region + jitStartOffset, region + region_size - jitStartOffset); } void Arm64Jit::InvalidateCacheAt(u32 em_address, int length) { @@ -198,7 +204,7 @@ void Arm64Jit::Compile(u32 em_address) { // Drat. The VFPU hit an uneaten prefix at the end of a block. if (js.startDefaultPrefix && js.MayHavePrefix()) { - WARN_LOG(JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4); + WARN_LOG_REPORT(JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4); js.LogPrefix(); // Let's try that one more time. We won't get back here because we toggled the value. diff --git a/Core/MIPS/ARM64/Arm64Jit.h b/Core/MIPS/ARM64/Arm64Jit.h index e8874fa0b5..28fdb16501 100644 --- a/Core/MIPS/ARM64/Arm64Jit.h +++ b/Core/MIPS/ARM64/Arm64Jit.h @@ -169,6 +169,7 @@ public: int Replace_fabsf() override; JitBlockCache *GetBlockCache() override { return &blocks; } + JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks; } std::vector SaveAndClearEmuHackOps() override { return blocks.SaveAndClearEmuHackOps(); } void RestoreSavedEmuHackOps(std::vector saved) override { blocks.RestoreSavedEmuHackOps(saved); } @@ -220,8 +221,8 @@ private: void BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely); // Utilities to reduce duplicated code - void CompImmLogic(MIPSGPReg rs, MIPSGPReg rt, u32 uimm, void (ARM64XEmitter::*arith)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg src, Arm64Gen::ARM64Reg src2), bool (ARM64XEmitter::*tryArithI2R)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg src, u32 val), u32 (*eval)(u32 a, u32 b)); - void CompType3(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, void (ARM64XEmitter::*arithOp2)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg rm, Arm64Gen::ARM64Reg rn), bool (ARM64XEmitter::*tryArithI2R)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg rm, u32 val), u32 (*eval)(u32 a, u32 b), bool symmetric = false); + void CompImmLogic(MIPSGPReg rs, MIPSGPReg rt, u32 uimm, void (ARM64XEmitter::*arith)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg src, Arm64Gen::ARM64Reg src2), bool (ARM64XEmitter::*tryArithI2R)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg src, u64 val), u32 (*eval)(u32 a, u32 b)); + void CompType3(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, void (ARM64XEmitter::*arithOp2)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg rm, Arm64Gen::ARM64Reg rn), bool (ARM64XEmitter::*tryArithI2R)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg rm, u64 val), u32 (*eval)(u32 a, u32 b), bool symmetric = false); void CompShiftImm(MIPSOpcode op, Arm64Gen::ShiftType shiftType, int sa); void CompShiftVar(MIPSOpcode op, Arm64Gen::ShiftType shiftType); void CompVrotShuffle(u8 *dregs, int imm, VectorSize sz, bool negSin); diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 53d07ec05f..dd2c83c302 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -45,6 +45,7 @@ void Arm64RegCache::Start(MIPSAnalyst::AnalysisResults &stats) { ar[i].mipsReg = MIPS_REG_INVALID; ar[i].isDirty = false; ar[i].pointerified = false; + ar[i].tempLocked = false; } for (int i = 0; i < NUM_MIPSREG; i++) { mr[i].loc = ML_MEM; @@ -57,7 +58,7 @@ void Arm64RegCache::Start(MIPSAnalyst::AnalysisResults &stats) { const StaticAllocation *statics = GetStaticAllocations(numStatics); for (int i = 0; i < numStatics; i++) { ar[statics[i].ar].mipsReg = statics[i].mr; - ar[statics[i].ar].pointerified = statics[i].pointerified; + ar[statics[i].ar].pointerified = statics[i].pointerified && jo_->enablePointerify; mr[statics[i].mr].loc = ML_ARMREG; mr[statics[i].mr].reg = statics[i].ar; mr[statics[i].mr].isStatic = true; @@ -68,10 +69,10 @@ void Arm64RegCache::Start(MIPSAnalyst::AnalysisResults &stats) { const ARM64Reg *Arm64RegCache::GetMIPSAllocationOrder(int &count) { // See register alloc remarks in Arm64Asm.cpp - // W19-W22 are most suitable for static allocation. Those that are chosen for static allocation + // W19-W23 are most suitable for static allocation. Those that are chosen for static allocation // should be omitted here and added in GetStaticAllocations. static const ARM64Reg allocationOrder[] = { - W19, W20, W21, W22, W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15, + W19, W20, W21, W22, W23, W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15, }; static const ARM64Reg allocationOrderStaticAlloc[] = { W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15, @@ -92,6 +93,7 @@ const Arm64RegCache::StaticAllocation *Arm64RegCache::GetStaticAllocations(int & {MIPS_REG_V0, W20}, {MIPS_REG_V1, W22}, {MIPS_REG_A0, W21}, + {MIPS_REG_RA, W23}, }; if (jo_->useStaticAlloc) { @@ -110,7 +112,7 @@ void Arm64RegCache::EmitLoadStaticRegisters() { for (int i = 0; i < count; i++) { int offset = GetMipsRegOffset(allocs[i].mr); emit_->LDR(INDEX_UNSIGNED, allocs[i].ar, CTXREG, offset); - if (allocs[i].pointerified) { + if (allocs[i].pointerified && jo_->enablePointerify) { emit_->MOVK(EncodeRegTo64(allocs[i].ar), ((uint64_t)Memory::base) >> 32, SHIFT_32); } } @@ -150,6 +152,8 @@ bool Arm64RegCache::IsMappedAsPointer(MIPSGPReg mipsReg) { if (ar[mr[mipsReg].reg].pointerified) { ELOG("Really shouldn't be pointerified here"); } + } else if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) { + return true; } return false; } @@ -211,6 +215,7 @@ void Arm64RegCache::MapRegTo(ARM64Reg reg, MIPSGPReg mipsReg, int mapFlags) { mr[mipsReg].loc = ML_ARMREG_IMM; break; default: + _assert_msg_(JIT, mr[mipsReg].loc != ML_ARMREG_AS_PTR, "MapRegTo with a pointer?"); mr[mipsReg].loc = ML_ARMREG; break; } @@ -223,6 +228,43 @@ void Arm64RegCache::MapRegTo(ARM64Reg reg, MIPSGPReg mipsReg, int mapFlags) { mr[mipsReg].reg = reg; } +ARM64Reg Arm64RegCache::AllocateReg() { + int allocCount; + const ARM64Reg *allocOrder = GetMIPSAllocationOrder(allocCount); + +allocate: + for (int i = 0; i < allocCount; i++) { + ARM64Reg reg = allocOrder[i]; + + if (ar[reg].mipsReg == MIPS_REG_INVALID && !ar[reg].tempLocked) { + return reg; + } + } + + // Still nothing. Let's spill a reg and goto 10. + // TODO: Use age or something to choose which register to spill? + // TODO: Spill dirty regs first? or opposite? + bool clobbered; + ARM64Reg bestToSpill = FindBestToSpill(true, &clobbered); + if (bestToSpill == INVALID_REG) { + bestToSpill = FindBestToSpill(false, &clobbered); + } + + if (bestToSpill != INVALID_REG) { + if (clobbered) { + DiscardR(ar[bestToSpill].mipsReg); + } else { + FlushArmReg(bestToSpill); + } + // Now one must be free. + goto allocate; + } + + // Uh oh, we have all of them spilllocked.... + ERROR_LOG_REPORT(JIT, "Out of spillable registers at PC %08x!!!", mips_->pc); + return INVALID_REG; +} + ARM64Reg Arm64RegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) { int allocCount; const ARM64Reg *allocOrder = GetMIPSAllocationOrder(allocCount); @@ -234,6 +276,8 @@ ARM64Reg Arm64RegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) { ARM64Reg reg = allocOrder[i]; if (ar[reg].mipsReg != MIPS_REG_INVALID && mr[ar[reg].mipsReg].spillLock) continue; + if (ar[reg].tempLocked) + continue; // As it's in alloc-order, we know it's not static so we don't need to check for that. @@ -261,6 +305,37 @@ ARM64Reg Arm64RegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) { return INVALID_REG; } +ARM64Reg Arm64RegCache::TryMapTempImm(MIPSGPReg r) { + // If already mapped, no need for a temporary. + if (IsMapped(r)) { + return R(r); + } + + if (mr[r].loc == ML_IMM) { + if (mr[r].imm == 0) { + return WZR; + } + + // Try our luck - check for an exact match in another armreg. + for (int i = 0; i < NUM_MIPSREG; ++i) { + if (mr[i].loc == ML_ARMREG_IMM && mr[i].imm == mr[r].imm) { + // Awesome, let's just use this reg. + return mr[i].reg; + } + } + } + + return INVALID_REG; +} + +ARM64Reg Arm64RegCache::GetAndLockTempR() { + ARM64Reg reg = AllocateReg(); + if (reg != INVALID_REG) { + ar[reg].tempLocked = true; + } + return reg; +} + // TODO: Somewhat smarter spilling - currently simply spills the first available, should do // round robin or FIFO or something. ARM64Reg Arm64RegCache::MapReg(MIPSGPReg mipsReg, int mapFlags) { @@ -288,6 +363,13 @@ ARM64Reg Arm64RegCache::MapReg(MIPSGPReg mipsReg, int mapFlags) { mr[mipsReg].loc = ML_ARMREG_IMM; ar[armReg].pointerified = false; } + } else if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) { + // Was mapped as pointer, now we want it mapped as a value, presumably to + // add or subtract stuff to it. + if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { + emit_->SUB(EncodeRegTo64(armReg), EncodeRegTo64(armReg), MEMBASEREG); + } + mr[mipsReg].loc = ML_ARMREG; } // Erasing the imm on dirty (necessary since otherwise we will still think it's ML_ARMREG_IMM and return // true for IsImm and calculate crazily wrong things). /unknown @@ -315,49 +397,35 @@ ARM64Reg Arm64RegCache::MapReg(MIPSGPReg mipsReg, int mapFlags) { } return mr[mipsReg].reg; + } else if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) { + // Was mapped as pointer, now we want it mapped as a value, presumably to + // add or subtract stuff to it. + if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { + emit_->SUB(EncodeRegTo64(armReg), EncodeRegTo64(armReg), MEMBASEREG); + } + mr[mipsReg].loc = ML_ARMREG; + if (mapFlags & MAP_DIRTY) { + ar[armReg].isDirty = true; + } + return (ARM64Reg)mr[mipsReg].reg; } // Okay, not mapped, so we need to allocate an ARM register. - - int allocCount; - const ARM64Reg *allocOrder = GetMIPSAllocationOrder(allocCount); - -allocate: - for (int i = 0; i < allocCount; i++) { - ARM64Reg reg = allocOrder[i]; - - if (ar[reg].mipsReg == MIPS_REG_INVALID) { - // That means it's free. Grab it, and load the value into it (if requested). - MapRegTo(reg, mipsReg, mapFlags); - return reg; - } + ARM64Reg reg = AllocateReg(); + if (reg != INVALID_REG) { + // Grab it, and load the value into it (if requested). + MapRegTo(reg, mipsReg, mapFlags); } - // Still nothing. Let's spill a reg and goto 10. - // TODO: Use age or something to choose which register to spill? - // TODO: Spill dirty regs first? or opposite? - bool clobbered; - ARM64Reg bestToSpill = FindBestToSpill(true, &clobbered); - if (bestToSpill == INVALID_REG) { - bestToSpill = FindBestToSpill(false, &clobbered); - } - - if (bestToSpill != INVALID_REG) { - if (clobbered) { - DiscardR(ar[bestToSpill].mipsReg); - } else { - FlushArmReg(bestToSpill); - } - // Now one must be free. - goto allocate; - } - - // Uh oh, we have all of them spilllocked.... - ERROR_LOG_REPORT(JIT, "Out of spillable registers at PC %08x!!!", mips_->pc); - return INVALID_REG; + return reg; } Arm64Gen::ARM64Reg Arm64RegCache::MapRegAsPointer(MIPSGPReg reg) { + // Already mapped. + if (mr[reg].loc == ML_ARMREG_AS_PTR) { + return mr[reg].reg; + } + ARM64Reg retval = INVALID_REG; if (mr[reg].loc != ML_ARMREG && mr[reg].loc != ML_ARMREG_IMM) { retval = MapReg(reg); @@ -368,9 +436,14 @@ Arm64Gen::ARM64Reg Arm64RegCache::MapRegAsPointer(MIPSGPReg reg) { if (mr[reg].loc == ML_ARMREG || mr[reg].loc == ML_ARMREG_IMM) { // If there was an imm attached, discard it. mr[reg].loc = ML_ARMREG; - int a = DecodeReg(mr[reg].reg); - if (!ar[a].pointerified) { - emit_->MOVK(ARM64Reg(X0 + a), ((uint64_t)Memory::base) >> 32, SHIFT_32); + ARM64Reg a = DecodeReg(mr[reg].reg); + if (!jo_->enablePointerify) { + // Convert to a pointer by adding the base and clearing off the top bits. + // If SP, we can probably avoid the top bit clear, let's play with that later. + emit_->ADD(EncodeRegTo64(a), EncodeRegTo64(a), MEMBASEREG); + mr[reg].loc = ML_ARMREG_AS_PTR; + } else if (!ar[a].pointerified) { + emit_->MOVK(EncodeRegTo64(a), ((uint64_t)Memory::base) >> 32, SHIFT_32); ar[a].pointerified = true; } } else { @@ -387,7 +460,7 @@ void Arm64RegCache::MapInIn(MIPSGPReg rd, MIPSGPReg rs) { SpillLock(rd, rs); MapReg(rd); MapReg(rs); - ReleaseSpillLocks(); + ReleaseSpillLock(rd, rs); } void Arm64RegCache::MapDirtyIn(MIPSGPReg rd, MIPSGPReg rs, bool avoidLoad) { @@ -395,7 +468,7 @@ void Arm64RegCache::MapDirtyIn(MIPSGPReg rd, MIPSGPReg rs, bool avoidLoad) { bool load = !avoidLoad || rd == rs; MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT); MapReg(rs); - ReleaseSpillLocks(); + ReleaseSpillLock(rd, rs); } void Arm64RegCache::MapDirtyInIn(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, bool avoidLoad) { @@ -404,7 +477,7 @@ void Arm64RegCache::MapDirtyInIn(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, bool MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT); MapReg(rt); MapReg(rs); - ReleaseSpillLocks(); + ReleaseSpillLock(rd, rs, rt); } void Arm64RegCache::MapDirtyDirtyIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, bool avoidLoad) { @@ -414,7 +487,7 @@ void Arm64RegCache::MapDirtyDirtyIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, MapReg(rd1, load1 ? MAP_DIRTY : MAP_NOINIT); MapReg(rd2, load2 ? MAP_DIRTY : MAP_NOINIT); MapReg(rs); - ReleaseSpillLocks(); + ReleaseSpillLock(rd1, rd2, rs); } void Arm64RegCache::MapDirtyDirtyInIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, MIPSGPReg rt, bool avoidLoad) { @@ -425,7 +498,7 @@ void Arm64RegCache::MapDirtyDirtyInIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs MapReg(rd2, load2 ? MAP_DIRTY : MAP_NOINIT); MapReg(rt); MapReg(rs); - ReleaseSpillLocks(); + ReleaseSpillLock(rd1, rd2, rs, rt); } void Arm64RegCache::FlushArmReg(ARM64Reg r) { @@ -450,10 +523,17 @@ void Arm64RegCache::FlushArmReg(ARM64Reg r) { mreg.loc = ML_IMM; mreg.reg = INVALID_REG; } else { - // Note: may be a 64-bit reg. - ARM64Reg storeReg = ARM64RegForFlush(ar[r].mipsReg); - if (storeReg != INVALID_REG) - emit_->STR(INDEX_UNSIGNED, storeReg, CTXREG, GetMipsRegOffset(ar[r].mipsReg)); + if (mreg.loc == ML_IMM || ar[r].isDirty) { + if (mreg.loc == ML_ARMREG_AS_PTR) { + // Unpointerify, in case dirty. + emit_->SUB(EncodeRegTo64(r), EncodeRegTo64(r), MEMBASEREG); + mreg.loc = ML_ARMREG; + } + // Note: may be a 64-bit reg. + ARM64Reg storeReg = ARM64RegForFlush(ar[r].mipsReg); + if (storeReg != INVALID_REG) + emit_->STR(INDEX_UNSIGNED, storeReg, CTXREG, GetMipsRegOffset(ar[r].mipsReg)); + } mreg.loc = ML_MEM; mreg.reg = INVALID_REG; mreg.imm = 0; @@ -465,9 +545,9 @@ void Arm64RegCache::FlushArmReg(ARM64Reg r) { void Arm64RegCache::DiscardR(MIPSGPReg mipsReg) { if (mr[mipsReg].isStatic) { - // Simply do nothing unless it's an IMM or ARMREG_IMM, in case we just switch it over to ARMREG, losing the value. - if (mr[mipsReg].loc == ML_ARMREG_IMM || mr[mipsReg].loc == ML_IMM) { - ARM64Reg armReg = mr[mipsReg].reg; + // Simply do nothing unless it's an IMM/ARMREG_IMM/ARMREG_AS_PTR, in case we just switch it over to ARMREG, losing the value. + ARM64Reg armReg = mr[mipsReg].reg; + if (mr[mipsReg].loc == ML_ARMREG_IMM || mr[mipsReg].loc == ML_IMM || mr[mipsReg].loc == ML_ARMREG_AS_PTR) { // Ignore the imm value, restore sanity mr[mipsReg].loc = ML_ARMREG; ar[armReg].pointerified = false; @@ -476,7 +556,7 @@ void Arm64RegCache::DiscardR(MIPSGPReg mipsReg) { return; } const RegMIPSLoc prevLoc = mr[mipsReg].loc; - if (prevLoc == ML_ARMREG || prevLoc == ML_ARMREG_IMM) { + if (prevLoc == ML_ARMREG || prevLoc == ML_ARMREG_IMM || prevLoc == ML_ARMREG_AS_PTR) { ARM64Reg armReg = mr[mipsReg].reg; ar[armReg].isDirty = false; ar[armReg].mipsReg = MIPS_REG_INVALID; @@ -532,6 +612,9 @@ ARM64Reg Arm64RegCache::ARM64RegForFlush(MIPSGPReg r) { } return mr[r].reg; + case ML_ARMREG_AS_PTR: + return INVALID_REG; + case ML_MEM: return INVALID_REG; @@ -578,6 +661,20 @@ void Arm64RegCache::FlushR(MIPSGPReg r) { ar[mr[r].reg].pointerified = false; break; + case ML_ARMREG_AS_PTR: + if (ar[mr[r].reg].isDirty) { + emit_->SUB(EncodeRegTo64(mr[r].reg), EncodeRegTo64(mr[r].reg), MEMBASEREG); + // We set this so ARM64RegForFlush knows it's no longer a pointer. + mr[r].loc = ML_ARMREG; + ARM64Reg storeReg = ARM64RegForFlush(r); + if (storeReg != INVALID_REG) { + emit_->STR(INDEX_UNSIGNED, storeReg, CTXREG, GetMipsRegOffset(r)); + } + ar[mr[r].reg].isDirty = false; + } + ar[mr[r].reg].mipsReg = MIPS_REG_INVALID; + break; + case ML_MEM: // Already there, nothing to do. break; @@ -596,6 +693,9 @@ void Arm64RegCache::FlushR(MIPSGPReg r) { } void Arm64RegCache::FlushAll() { + // Note: make sure not to change the registers when flushing: + // Branching code expects the armreg to retain its value. + // LO can't be included in a 32-bit pair, since it's 64 bit. // Flush it first so we don't get it confused. FlushR(MIPS_REG_LO); @@ -654,6 +754,9 @@ void Arm64RegCache::FlushAll() { ar[armReg].pointerified = false; } mr[i].loc = ML_ARMREG; + } else if (mr[i].loc == ML_ARMREG_AS_PTR) { + emit_->SUB(EncodeRegTo64(armReg), EncodeRegTo64(armReg), MEMBASEREG); + mr[i].loc = ML_ARMREG; } if (i != MIPS_REG_ZERO && mr[i].reg == INVALID_REG) { ELOG("ARM reg of static %i is invalid", i); @@ -667,7 +770,7 @@ void Arm64RegCache::FlushAll() { int count = 0; const StaticAllocation *allocs = GetStaticAllocations(count); for (int i = 0; i < count; i++) { - if (allocs[i].pointerified && !ar[allocs[i].ar].pointerified) { + if (allocs[i].pointerified && !ar[allocs[i].ar].pointerified && jo_->enablePointerify) { // Re-pointerify emit_->MOVK(EncodeRegTo64(allocs[i].ar), ((uint64_t)Memory::base) >> 32, SHIFT_32); ar[allocs[i].ar].pointerified = true; @@ -770,16 +873,25 @@ void Arm64RegCache::SpillLock(MIPSGPReg r1, MIPSGPReg r2, MIPSGPReg r3, MIPSGPRe if (r4 != MIPS_REG_INVALID) mr[r4].spillLock = true; } -void Arm64RegCache::ReleaseSpillLocks() { +void Arm64RegCache::ReleaseSpillLocksAndDiscardTemps() { for (int i = 0; i < NUM_MIPSREG; i++) { if (!mr[i].isStatic) mr[i].spillLock = false; } + for (int i = 0; i < NUM_ARMREG; i++) { + ar[i].tempLocked = false; + } } -void Arm64RegCache::ReleaseSpillLock(MIPSGPReg reg) { - if (!mr[reg].isStatic) - mr[reg].spillLock = false; +void Arm64RegCache::ReleaseSpillLock(MIPSGPReg r1, MIPSGPReg r2, MIPSGPReg r3, MIPSGPReg r4) { + if (!mr[r1].isStatic) + mr[r1].spillLock = false; + if (r2 != MIPS_REG_INVALID && !mr[r2].isStatic) + mr[r2].spillLock = false; + if (r3 != MIPS_REG_INVALID && !mr[r3].isStatic) + mr[r3].spillLock = false; + if (r4 != MIPS_REG_INVALID && !mr[r4].isStatic) + mr[r4].spillLock = false; } ARM64Reg Arm64RegCache::R(MIPSGPReg mipsReg) { @@ -792,7 +904,9 @@ ARM64Reg Arm64RegCache::R(MIPSGPReg mipsReg) { } ARM64Reg Arm64RegCache::RPtr(MIPSGPReg mipsReg) { - if (mr[mipsReg].loc == ML_ARMREG || mr[mipsReg].loc == ML_ARMREG_IMM) { + if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) { + return (ARM64Reg)mr[mipsReg].reg; + } else if (mr[mipsReg].loc == ML_ARMREG || mr[mipsReg].loc == ML_ARMREG_IMM) { int a = mr[mipsReg].reg; if (ar[a].pointerified) { return (ARM64Reg)mr[mipsReg].reg; diff --git a/Core/MIPS/ARM64/Arm64RegCache.h b/Core/MIPS/ARM64/Arm64RegCache.h index d58a650688..b95caf7854 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.h +++ b/Core/MIPS/ARM64/Arm64RegCache.h @@ -23,8 +23,7 @@ namespace Arm64JitConstants { -const Arm64Gen::ARM64Reg DOWNCOUNTREG = Arm64Gen::W23; -const Arm64Gen::ARM64Reg OTHERTEMPREG = Arm64Gen::W24; +const Arm64Gen::ARM64Reg DOWNCOUNTREG = Arm64Gen::W24; const Arm64Gen::ARM64Reg FLAGTEMPREG = Arm64Gen::X25; const Arm64Gen::ARM64Reg JITBASEREG = Arm64Gen::X26; const Arm64Gen::ARM64Reg CTXREG = Arm64Gen::X27; @@ -41,6 +40,8 @@ enum { enum RegMIPSLoc { ML_IMM, ML_ARMREG, + // In an arm reg, but an adjusted pointer (not pointerified - unaligned.) + ML_ARMREG_AS_PTR, // In an arm reg, but also has a known immediate value. ML_ARMREG_IMM, ML_MEM, @@ -60,6 +61,7 @@ struct RegARM64 { MIPSGPReg mipsReg; // if -1, no mipsreg attached. bool isDirty; // Should the register be written back? bool pointerified; // Has used movk to move the memory base into the top part of the reg. Note - still usable as 32-bit reg! + bool tempLocked; // Reserved for a temp register. }; struct RegMIPS { @@ -89,8 +91,8 @@ public: // Protect the arm register containing a MIPS register from spilling, to ensure that // it's being kept allocated. void SpillLock(MIPSGPReg reg, MIPSGPReg reg2 = MIPS_REG_INVALID, MIPSGPReg reg3 = MIPS_REG_INVALID, MIPSGPReg reg4 = MIPS_REG_INVALID); - void ReleaseSpillLock(MIPSGPReg reg); - void ReleaseSpillLocks(); + void ReleaseSpillLock(MIPSGPReg reg, MIPSGPReg reg2 = MIPS_REG_INVALID, MIPSGPReg reg3 = MIPS_REG_INVALID, MIPSGPReg reg4 = MIPS_REG_INVALID); + void ReleaseSpillLocksAndDiscardTemps(); void SetImm(MIPSGPReg reg, u64 immVal); bool IsImm(MIPSGPReg reg) const; @@ -99,6 +101,9 @@ public: // Optimally set a register to an imm value (possibly using another register.) void SetRegImm(Arm64Gen::ARM64Reg reg, u64 imm); + // May fail and return INVALID_REG if it needs flushing. + Arm64Gen::ARM64Reg TryMapTempImm(MIPSGPReg); + // Returns an ARM register containing the requested MIPS register. Arm64Gen::ARM64Reg MapReg(MIPSGPReg reg, int mapFlags = 0); Arm64Gen::ARM64Reg MapRegAsPointer(MIPSGPReg reg); @@ -120,6 +125,8 @@ public: void FlushR(MIPSGPReg r); void DiscardR(MIPSGPReg r); + Arm64Gen::ARM64Reg GetAndLockTempR(); + Arm64Gen::ARM64Reg R(MIPSGPReg preg); // Returns a cached register, while checking that it's NOT mapped as a pointer Arm64Gen::ARM64Reg RPtr(MIPSGPReg preg); // Returns a cached register, if it has been mapped as a pointer @@ -143,6 +150,7 @@ private: const StaticAllocation *GetStaticAllocations(int &count); const Arm64Gen::ARM64Reg *GetMIPSAllocationOrder(int &count); void MapRegTo(Arm64Gen::ARM64Reg reg, MIPSGPReg mipsReg, int mapFlags); + Arm64Gen::ARM64Reg AllocateReg(); Arm64Gen::ARM64Reg FindBestToSpill(bool unusedOnly, bool *clobbered); Arm64Gen::ARM64Reg ARM64RegForFlush(MIPSGPReg r); diff --git a/Core/MIPS/IR/IRCompALU.cpp b/Core/MIPS/IR/IRCompALU.cpp index c7074bed79..70fa758dac 100644 --- a/Core/MIPS/IR/IRCompALU.cpp +++ b/Core/MIPS/IR/IRCompALU.cpp @@ -40,14 +40,15 @@ // #define CONDITIONAL_DISABLE { Comp_Generic(op); return; } #define CONDITIONAL_DISABLE ; #define DISABLE { Comp_Generic(op); return; } +#define INVALIDOP { Comp_Generic(op); return; } namespace MIPSComp { void IRFrontend::Comp_IType(MIPSOpcode op) { CONDITIONAL_DISABLE; - s32 simm = (s32)(s16)(op & 0xFFFF); // sign extension - u32 uimm = op & 0xFFFF; + s32 simm = (s32)_IMM16; // sign extension + u32 uimm = (u16)_IMM16; u32 suimm = (u32)(s32)simm; MIPSGPReg rt = _RT; @@ -80,7 +81,7 @@ void IRFrontend::Comp_IType(MIPSOpcode op) { break; default: - Comp_Generic(op); + INVALIDOP; break; } } @@ -104,7 +105,7 @@ void IRFrontend::Comp_RType2(MIPSOpcode op) { ir.Write(IROp::Clz, rd, IRTEMP_0); break; default: - Comp_Generic(op); + INVALIDOP; break; } } @@ -176,7 +177,7 @@ void IRFrontend::Comp_RType3(MIPSOpcode op) { break; default: - Comp_Generic(op); + INVALIDOP; break; } } @@ -187,7 +188,7 @@ void IRFrontend::CompShiftImm(MIPSOpcode op, IROp shiftOpImm, int sa) { ir.Write(shiftOpImm, rd, rt, sa); } -void IRFrontend::CompShiftVar(MIPSOpcode op, IROp shiftOp, IROp shiftOpImm) { +void IRFrontend::CompShiftVar(MIPSOpcode op, IROp shiftOp) { MIPSGPReg rd = _RD; MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; @@ -210,11 +211,12 @@ void IRFrontend::Comp_ShiftType(MIPSOpcode op) { case 0: CompShiftImm(op, IROp::ShlImm, sa); break; //sll case 2: CompShiftImm(op, (rs == 1 ? IROp::RorImm : IROp::ShrImm), sa); break; //srl case 3: CompShiftImm(op, IROp::SarImm, sa); break; //sra - case 4: CompShiftVar(op, IROp::Shl, IROp::ShlImm); break; //sllv - case 6: CompShiftVar(op, (sa == 1 ? IROp::Ror : IROp::Shr), (sa == 1 ? IROp::RorImm : IROp::ShrImm)); break; //srlv - case 7: CompShiftVar(op, IROp::Sar, IROp::SarImm); break; //srav + case 4: CompShiftVar(op, IROp::Shl); break; //sllv + case 6: CompShiftVar(op, (sa == 1 ? IROp::Ror : IROp::Shr)); break; //srlv + case 7: CompShiftVar(op, IROp::Sar); break; //srav + default: - Comp_Generic(op); + INVALIDOP; break; } } @@ -256,7 +258,7 @@ void IRFrontend::Comp_Special3(MIPSOpcode op) { break; default: - Comp_Generic(op); + INVALIDOP; break; } } @@ -285,7 +287,7 @@ void IRFrontend::Comp_Allegrex(MIPSOpcode op) { break; default: - Comp_Generic(op); + INVALIDOP; return; } } @@ -307,7 +309,7 @@ void IRFrontend::Comp_Allegrex2(MIPSOpcode op) { ir.Write(IROp::BSwap32, rd, rt); break; default: - Comp_Generic(op); + INVALIDOP; break; } } @@ -372,7 +374,7 @@ void IRFrontend::Comp_MulDivType(MIPSOpcode op) { break; default: - DISABLE; + INVALIDOP; } } diff --git a/Core/MIPS/IR/IRCompBranch.cpp b/Core/MIPS/IR/IRCompBranch.cpp index f9185b3bdd..20d352b5c1 100644 --- a/Core/MIPS/IR/IRCompBranch.cpp +++ b/Core/MIPS/IR/IRCompBranch.cpp @@ -92,7 +92,7 @@ void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) { CompileDelaySlot(); int dcAmount = js.downcountAmount; - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; FlushAll(); @@ -104,6 +104,8 @@ void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) { FlushAll(); ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr)); + // Account for the delay slot. + js.compilerPC += 4; js.compiling = false; } @@ -131,7 +133,7 @@ void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink, CompileDelaySlot(); int dcAmount = js.downcountAmount; - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; FlushAll(); @@ -141,6 +143,9 @@ void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink, // Taken FlushAll(); ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr)); + + // Account for the delay slot. + js.compilerPC += 4; js.compiling = false; } @@ -195,7 +200,7 @@ void IRFrontend::BranchFPFlag(MIPSOpcode op, IRComparison cc, bool likely) { CompileDelaySlot(); int dcAmount = js.downcountAmount; - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; FlushAll(); @@ -206,6 +211,9 @@ void IRFrontend::BranchFPFlag(MIPSOpcode op, IRComparison cc, bool likely) { CompileDelaySlot(); FlushAll(); ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr)); + + // Account for the delay slot. + js.compilerPC += 4; js.compiling = false; } @@ -241,7 +249,7 @@ void IRFrontend::BranchVFPUFlag(MIPSOpcode op, IRComparison cc, bool likely) { CompileDelaySlot(); int dcAmount = js.downcountAmount; - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; if (delaySlotIsBranch && (signed short)(delaySlotOp & 0xFFFF) != (signed short)(op & 0xFFFF) - 1) @@ -261,6 +269,9 @@ void IRFrontend::BranchVFPUFlag(MIPSOpcode op, IRComparison cc, bool likely) { // Taken FlushAll(); ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr)); + + // Account for the delay slot. + js.compilerPC += 4; js.compiling = false; } @@ -284,11 +295,12 @@ void IRFrontend::Comp_Jump(MIPSOpcode op) { // Might be a stubbed address or something? if (!Memory::IsValidAddress(targetAddr)) { - if (js.nextExit == 0) { + // If preloading, flush - this block will likely be fixed later. + if (js.preloading) + js.cancel = true; + else ERROR_LOG_REPORT(JIT, "Jump to invalid address: %08x", targetAddr); - } else { - js.compiling = false; - } + js.compiling = false; // TODO: Mark this block dirty or something? May be indication it will be changed by imports. return; } @@ -309,11 +321,14 @@ void IRFrontend::Comp_Jump(MIPSOpcode op) { } int dcAmount = js.downcountAmount; - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; FlushAll(); ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr)); + + // Account for the delay slot. + js.compilerPC += 4; js.compiling = false; } @@ -338,6 +353,9 @@ void IRFrontend::Comp_JumpReg(MIPSOpcode op) { ir.WriteSetConstant(rd, GetCompilerPC() + 8); CompileDelaySlot(); // Syscall (the delay slot) does FlushAll. + + // Account for the delay slot itself in total bytes. + js.compilerPC += 4; return; // Syscall (delay slot) wrote exit code. } else if (delaySlotIsNice) { if (andLink) @@ -367,17 +385,20 @@ void IRFrontend::Comp_JumpReg(MIPSOpcode op) { } int dcAmount = js.downcountAmount; - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; ir.Write(IROp::ExitToReg, 0, destReg, 0); + + // Account for the delay slot. + js.compilerPC += 4; js.compiling = false; } void IRFrontend::Comp_Syscall(MIPSOpcode op) { // Note: If we're in a delay slot, this is off by one compared to the interpreter. int dcAmount = js.downcountAmount + (js.inDelaySlot ? -1 : 0); - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; // If not in a delay slot, we need to update PC. diff --git a/Core/MIPS/IR/IRCompFPU.cpp b/Core/MIPS/IR/IRCompFPU.cpp index 96c39acc8d..ce01c7ff75 100644 --- a/Core/MIPS/IR/IRCompFPU.cpp +++ b/Core/MIPS/IR/IRCompFPU.cpp @@ -51,6 +51,7 @@ // #define CONDITIONAL_DISABLE { Comp_Generic(op); return; } #define CONDITIONAL_DISABLE ; #define DISABLE { Comp_Generic(op); return; } +#define INVALIDOP { Comp_Generic(op); return; } namespace MIPSComp { @@ -67,7 +68,7 @@ void IRFrontend::Comp_FPU3op(MIPSOpcode op) { case 2: ir.Write(IROp::FMul, fd, fs, ft); break; //F(fd) = F(fs) * F(ft); //mul case 3: ir.Write(IROp::FDiv, fd, fs, ft); break; //F(fd) = F(fs) / F(ft); //div default: - DISABLE; + INVALIDOP; return; } } @@ -90,7 +91,7 @@ void IRFrontend::Comp_FPULS(MIPSOpcode op) { break; default: - _dbg_assert_msg_(CPU, 0, "Trying to interpret FPULS instruction that can't be interpreted"); + INVALIDOP; break; } } @@ -110,7 +111,7 @@ void IRFrontend::Comp_FPUComp(MIPSOpcode op) { IRFpCompareMode mode; switch (opc) { case 1: // un, ngle (unordered) - mode = IRFpCompareMode::NotEqualUnordered; + mode = IRFpCompareMode::EitherUnordered; break; case 2: // eq, seq (equal, ordered) mode = IRFpCompareMode::EqualOrdered; @@ -131,7 +132,7 @@ void IRFrontend::Comp_FPUComp(MIPSOpcode op) { mode = IRFpCompareMode::LessEqualUnordered; break; default: - DISABLE; + INVALIDOP; return; } ir.Write(IROp::FCmp, (int)mode, fs, ft); @@ -158,27 +159,17 @@ void IRFrontend::Comp_FPU2op(MIPSOpcode op) { break; case 12: //FsI(fd) = (int)floorf(F(fs)+0.5f); break; //round.w.s - { ir.Write(IROp::FRound, fd, fs); break; - } - case 13: //FsI(fd) = Rto0(F(fs))); break; //trunc.w.s - { ir.Write(IROp::FTrunc, fd, fs); break; - } - case 14://FsI(fd) = (int)ceilf (F(fs)); break; //ceil.w.s - { ir.Write(IROp::FCeil, fd, fs); break; - } case 15: //FsI(fd) = (int)floorf(F(fs)); break; //floor.w.s - { ir.Write(IROp::FFloor, fd, fs); break; - } case 32: //F(fd) = (float)FsI(fs); break; //cvt.s.w ir.Write(IROp::FCvtSW, fd, fs); @@ -189,7 +180,7 @@ void IRFrontend::Comp_FPU2op(MIPSOpcode op) { break; default: - DISABLE; + INVALIDOP; } } @@ -234,7 +225,7 @@ void IRFrontend::Comp_mxc1(MIPSOpcode op) { } return; default: - DISABLE; + INVALIDOP; break; } } diff --git a/Core/MIPS/IR/IRCompLoadStore.cpp b/Core/MIPS/IR/IRCompLoadStore.cpp index 50130fc583..4ef50d5e0b 100644 --- a/Core/MIPS/IR/IRCompLoadStore.cpp +++ b/Core/MIPS/IR/IRCompLoadStore.cpp @@ -41,16 +41,13 @@ // #define CONDITIONAL_DISABLE { Comp_Generic(op); return; } #define CONDITIONAL_DISABLE ; #define DISABLE { Comp_Generic(op); return; } +#define INVALIDOP { Comp_Generic(op); return; } namespace MIPSComp { - void IRFrontend::Comp_ITypeMemLR(MIPSOpcode op, bool load) { - DISABLE; - } - void IRFrontend::Comp_ITypeMem(MIPSOpcode op) { CONDITIONAL_DISABLE; - int offset = (signed short)(op & 0xFFFF); + int offset = _IMM16; MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; int o = op >> 26; @@ -61,7 +58,6 @@ namespace MIPSComp { CheckMemoryBreakpoint(rs, offset); - int addrReg = IRTEMP_0; switch (o) { // Load case 35: @@ -91,18 +87,27 @@ namespace MIPSComp { break; case 34: //lwl - case 38: //lwr - case 42: //swl - case 46: //swr - DISABLE; + ir.Write(IROp::Load32Left, rt, rs, ir.AddConstant(offset)); break; + case 38: //lwr + ir.Write(IROp::Load32Right, rt, rs, ir.AddConstant(offset)); + break; + case 42: //swl + ir.Write(IROp::Store32Left, rt, rs, ir.AddConstant(offset)); + break; + case 46: //swr + ir.Write(IROp::Store32Right, rt, rs, ir.AddConstant(offset)); + break; + default: - Comp_Generic(op); + INVALIDOP; return; } } void IRFrontend::Comp_Cache(MIPSOpcode op) { + CONDITIONAL_DISABLE; + // int imm = (s16)(op & 0xFFFF); // int rs = _RS; // int addr = R(rs) + imm; diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index 5cc586a198..63fe98b033 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -333,7 +333,6 @@ namespace MIPSComp { case 53: // lvl/lvr.q - highly unusual case 61: // svl/svr.q - highly unusual - logBlocks = 1; DISABLE; break; @@ -618,17 +617,16 @@ namespace MIPSComp { GetVectorRegsPrefixD(dregs, sz, _VD); u8 tempregs[4]; - bool usingTemps = false; for (int i = 0; i < n; i++) { if (!IsOverlapSafe(dregs[i], n, sregs, n, tregs)) { tempregs[i] = IRVTEMP_0 + i; - usingTemps = true; } else { tempregs[i] = dregs[i]; } } - if (allowSIMD && sz == V_Quad && !usingTemps && IsConsecutive4(dregs) && IsConsecutive4(sregs) && IsConsecutive4(tregs)) { + // If all three are consecutive 4, we're safe regardless of if we use temps so we should not check that here. + if (allowSIMD && sz == V_Quad && IsConsecutive4(dregs) && IsConsecutive4(sregs) && IsConsecutive4(tregs)) { IROp opFunc = IROp::Nop; switch (op >> 26) { case 24: //VFPU0 @@ -1643,7 +1641,7 @@ namespace MIPSComp { GetVectorRegsPrefixS(sregs, sz, _VS); GetVectorRegsPrefixT(tregs, sz, _VT); - VCondition cond = (VCondition)(op & 0xF); + int cond = op & 0xF; int mask = 0; for (int i = 0; i < n; i++) { ir.Write(IROp::FCmpVfpuBit, cond | (i << 4), sregs[i], tregs[i]); @@ -1929,7 +1927,7 @@ namespace MIPSComp { ir.Write(IROp::FSub, tempregs[3], sregs[2], sregs[3]); } } else { - DISABLE; + INVALIDOP; } for (int i = 0; i < n; ++i) { diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index 17985ab835..d7ca005e65 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -32,8 +32,6 @@ namespace MIPSComp { IRFrontend::IRFrontend(bool startDefaultPrefix) { - logBlocks = 0; - dontLogBlocks = 0; js.startDefaultPrefix = true; js.hasSetRounding = false; // js.currentRoundingFunc = convertS0ToSCRATCH1[0]; @@ -105,7 +103,7 @@ void IRFrontend::CompileDelaySlot() { js.inDelaySlot = false; } -bool IRFrontend::CheckRounding() { +bool IRFrontend::CheckRounding(u32 blockAddress) { bool cleanSlate = false; if (js.hasSetRounding && !js.lastSetRounding) { WARN_LOG(JIT, "Detected rounding mode usage, rebuilding jit with checks"); @@ -116,7 +114,7 @@ bool IRFrontend::CheckRounding() { // Drat. The VFPU hit an uneaten prefix at the end of a block. if (js.startDefaultPrefix && js.MayHavePrefix()) { - WARN_LOG(JIT, "An uneaten prefix at end of block"); + WARN_LOG_REPORT(JIT, "An uneaten prefix at end of block for %08x", blockAddress); logBlocks = 1; js.LogPrefix(); @@ -164,7 +162,7 @@ void IRFrontend::Comp_ReplacementFunc(MIPSOpcode op) { MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this); } else { ApplyRoundingMode(); - ir.Write(IROp::Downcount, 0, js.downcountAmount & 0xFF, js.downcountAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(js.downcountAmount)); ir.Write(IROp::ExitToReg, 0, MIPS_REG_RA, 0); js.compiling = false; } @@ -221,8 +219,9 @@ MIPSOpcode IRFrontend::GetOffsetInstruction(int offset) { return Memory::Read_Instruction(GetCompilerPC() + 4 * offset); } -void IRFrontend::DoJit(u32 em_address, std::vector &instructions, std::vector &constants, u32 &mipsBytes) { +void IRFrontend::DoJit(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload) { js.cancel = false; + js.preloading = preload; js.blockStart = em_address; js.compilerPC = em_address; js.lastContinuedPC = 0; @@ -246,12 +245,11 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, std::v MIPSCompileOp(inst, this); js.compilerPC += 4; js.numInstructions++; + } - if (ir.GetConstants().size() > 64) { - // Need to break the block - ir.Write(IROp::ExitToConst, ir.AddConstant(js.compilerPC)); - js.compiling = false; - } + if (js.cancel) { + // Clear the instructions to signal this was not compiled. + ir.Clear(); } mipsBytes = js.compilerPC - em_address; @@ -260,6 +258,7 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, std::v IRWriter *code = &ir; if (!js.hadBreakpoints) { static const IRPassFunc passes[] = { + &RemoveLoadStoreLeftRight, &OptimizeFPMoves, &PropagateConstants, &PurgeTemps, @@ -267,7 +266,7 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, std::v // &MergeLoadStore, // &ThreeOpToTwoOp, }; - if (IRApplyPasses(passes, ARRAY_SIZE(passes), ir, simplified)) + if (IRApplyPasses(passes, ARRAY_SIZE(passes), ir, simplified, opts)) logBlocks = 1; code = &simplified; //if (ir.GetInstructions().size() >= 24) @@ -275,36 +274,35 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, std::v } instructions = code->GetInstructions(); - constants = code->GetConstants(); if (logBlocks > 0 && dontLogBlocks == 0) { char temp2[256]; - ILOG("=============== mips %08x ===============", em_address); - for (u32 cpc = em_address; cpc != GetCompilerPC() + 4; cpc += 4) { + NOTICE_LOG(JIT, "=============== mips %08x ===============", em_address); + for (u32 cpc = em_address; cpc != GetCompilerPC(); cpc += 4) { temp2[0] = 0; MIPSDisAsm(Memory::Read_Opcode_JIT(cpc), cpc, temp2, true); - ILOG("M: %08x %s", cpc, temp2); + NOTICE_LOG(JIT, "M: %08x %s", cpc, temp2); } } if (logBlocks > 0 && dontLogBlocks == 0) { - ILOG("=============== Original IR (%d instructions, %d const) ===============", (int)ir.GetInstructions().size(), (int)ir.GetConstants().size()); + NOTICE_LOG(JIT, "=============== Original IR (%d instructions) ===============", (int)ir.GetInstructions().size()); for (size_t i = 0; i < ir.GetInstructions().size(); i++) { char buf[256]; - DisassembleIR(buf, sizeof(buf), ir.GetInstructions()[i], &ir.GetConstants()[0]); - ILOG("%s", buf); + DisassembleIR(buf, sizeof(buf), ir.GetInstructions()[i]); + NOTICE_LOG(JIT, "%s", buf); } - ILOG("=============== end ================="); + NOTICE_LOG(JIT, "=============== end ================="); } if (logBlocks > 0 && dontLogBlocks == 0) { - ILOG("=============== IR (%d instructions, %d const) ===============", (int)code->GetInstructions().size(), (int)code->GetConstants().size()); + NOTICE_LOG(JIT, "=============== IR (%d instructions) ===============", (int)code->GetInstructions().size()); for (size_t i = 0; i < code->GetInstructions().size(); i++) { char buf[256]; - DisassembleIR(buf, sizeof(buf), code->GetInstructions()[i], &code->GetConstants()[0]); - ILOG("%s", buf); + DisassembleIR(buf, sizeof(buf), code->GetInstructions()[i]); + NOTICE_LOG(JIT, "%s", buf); } - ILOG("=============== end ================="); + NOTICE_LOG(JIT, "=============== end ================="); } if (logBlocks > 0) @@ -328,7 +326,7 @@ void IRFrontend::CheckBreakpoint(u32 addr) { // TODO: In likely branches, downcount will be incorrect. int downcountOffset = js.inDelaySlot && js.downcountAmount >= 2 ? -2 : 0; int downcountAmount = js.downcountAmount + downcountOffset; - ir.Write(IROp::Downcount, 0, downcountAmount & 0xFF, downcountAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(downcountAmount)); // Note that this means downcount can't be metadata on the block. js.downcountAmount = -downcountOffset; ir.Write(IROp::Breakpoint); @@ -351,7 +349,7 @@ void IRFrontend::CheckMemoryBreakpoint(int rs, int offset) { downcountOffset = 0; } int downcountAmount = js.downcountAmount + downcountOffset; - ir.Write(IROp::Downcount, 0, downcountAmount & 0xFF, downcountAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(downcountAmount)); // Note that this means downcount can't be metadata on the block. js.downcountAmount = -downcountOffset; ir.Write(IROp::MemoryCheck, 0, rs, ir.AddConstant(offset)); diff --git a/Core/MIPS/IR/IRFrontend.h b/Core/MIPS/IR/IRFrontend.h index b3fb8cdecf..0f9a1ab418 100644 --- a/Core/MIPS/IR/IRFrontend.h +++ b/Core/MIPS/IR/IRFrontend.h @@ -86,14 +86,18 @@ public: int Replace_fabsf() override; void DoState(PointerWrap &p); - bool CheckRounding(); // returns true if we need a do-over + bool CheckRounding(u32 blockAddress); // returns true if we need a do-over - void DoJit(u32 em_address, std::vector &instructions, std::vector &constants, u32 &mipsBytes); + void DoJit(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload); void EatPrefix() override { js.EatPrefix(); } + void SetOptions(const IROptions &o) { + opts = o; + } + private: void RestoreRoundingMode(bool force = false); void ApplyRoundingMode(bool force = false); @@ -118,7 +122,7 @@ private: // Utilities to reduce duplicated code void CompShiftImm(MIPSOpcode op, IROp shiftType, int sa); - void CompShiftVar(MIPSOpcode op, IROp shiftType, IROp shiftTypeConst); + void CompShiftVar(MIPSOpcode op, IROp shiftType); void ApplyPrefixST(u8 *vregs, u32 prefix, VectorSize sz, int tempReg); void ApplyPrefixD(const u8 *vregs, VectorSize sz); @@ -128,15 +132,13 @@ private: void GetVectorRegs(u8 regs[4], VectorSize N, int vectorReg); void GetMatrixRegs(u8 regs[16], MatrixSize N, int matrixReg); - // Utils - void Comp_ITypeMemLR(MIPSOpcode op, bool load); - // State JitState js; IRWriter ir; + IROptions opts{}; - int dontLogBlocks; - int logBlocks; + int dontLogBlocks = 0; + int logBlocks = 0; }; } // namespace diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 2c216bf9c5..7ad98f849f 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -71,11 +71,15 @@ static const IRMeta irMeta[] = { { IROp::Load16, "Load16", "GGC" }, { IROp::Load16Ext, "Load16Ext", "GGC" }, { IROp::Load32, "Load32", "GGC" }, + { IROp::Load32Left, "Load32Left", "GGC", IRFLAG_SRC3DST }, + { IROp::Load32Right, "Load32Right", "GGC", IRFLAG_SRC3DST }, { IROp::LoadFloat, "LoadFloat", "FGC" }, { IROp::LoadVec4, "LoadVec4", "VGC" }, { IROp::Store8, "Store8", "GGC", IRFLAG_SRC3 }, { IROp::Store16, "Store16", "GGC", IRFLAG_SRC3 }, { IROp::Store32, "Store32", "GGC", IRFLAG_SRC3 }, + { IROp::Store32Left, "Store32Left", "GGC", IRFLAG_SRC3 }, + { IROp::Store32Right, "Store32Right", "GGC", IRFLAG_SRC3 }, { IROp::StoreFloat, "StoreFloat", "FGC", IRFLAG_SRC3 }, { IROp::StoreVec4, "StoreVec4", "VGC", IRFLAG_SRC3 }, { IROp::FAdd, "FAdd", "FFF" }, @@ -141,7 +145,7 @@ static const IRMeta irMeta[] = { { IROp::Vec2Pack31To16, "Vec2Pack31To16", "2V" }, { IROp::Interpret, "Interpret", "_C" }, - { IROp::Downcount, "Downcount", "_II" }, + { IROp::Downcount, "Downcount", "_C" }, { IROp::ExitToPC, "ExitToPC", "", IRFLAG_EXIT }, { IROp::ExitToConst, "Exit", "C", IRFLAG_EXIT }, { IROp::ExitToConstIfEq, "ExitIfEq", "CGG", IRFLAG_EXIT }, @@ -174,7 +178,10 @@ void IRWriter::Write(IROp op, u8 dst, u8 src1, u8 src2) { inst.dest = dst; inst.src1 = src1; inst.src2 = src2; + inst.constant = nextConst_; insts_.push_back(inst); + + nextConst_ = 0; } void IRWriter::WriteSetConstant(u8 dst, u32 value) { @@ -182,16 +189,8 @@ void IRWriter::WriteSetConstant(u8 dst, u32 value) { } int IRWriter::AddConstant(u32 value) { - for (size_t i = 0; i < constPool_.size(); i++) { - if (constPool_[i] == value) - return (int)i; - } - constPool_.push_back(value); - if (constPool_.size() > 255) { - // Cannot have more than 256 constants in a block! - Crash(); - } - return (int)constPool_.size() - 1; + nextConst_ = value; + return 255; } int IRWriter::AddConstantFloat(float value) { @@ -207,13 +206,19 @@ const char *GetGPRName(int r) { switch (r) { case IRTEMP_0: return "irtemp0"; case IRTEMP_1: return "irtemp1"; + case IRTEMP_2: return "irtemp2"; + case IRTEMP_3: return "irtemp3"; case IRTEMP_LHS: return "irtemp_lhs"; case IRTEMP_RHS: return "irtemp_rhs"; + case IRTEMP_LR_ADDR: return "irtemp_addr"; + case IRTEMP_LR_VALUE: return "irtemp_value"; + case IRTEMP_LR_MASK: return "irtemp_mask"; + case IRTEMP_LR_SHIFT: return "irtemp_shift"; default: return "(unk)"; } } -void DisassembleParam(char *buf, int bufSize, u8 param, char type, const u32 *constPool) { +void DisassembleParam(char *buf, int bufSize, u8 param, char type, u32 constant) { static const char *vfpuCtrlNames[VFPU_CTRL_MAX] = { "SPFX", "TPFX", @@ -269,7 +274,7 @@ void DisassembleParam(char *buf, int bufSize, u8 param, char type, const u32 *co } break; case 'C': - snprintf(buf, bufSize, "%08x", constPool[param]); + snprintf(buf, bufSize, "%08x", constant); break; case 'I': snprintf(buf, bufSize, "%02x", param); @@ -300,7 +305,7 @@ const IRMeta *GetIRMeta(IROp op) { return metaIndex[(int)op]; } -void DisassembleIR(char *buf, size_t bufsize, IRInst inst, const u32 *constPool) { +void DisassembleIR(char *buf, size_t bufsize, IRInst inst) { const IRMeta *meta = GetIRMeta(inst.op); if (!meta) { snprintf(buf, bufsize, "Unknown %d", (int)inst.op); @@ -309,9 +314,9 @@ void DisassembleIR(char *buf, size_t bufsize, IRInst inst, const u32 *constPool) char bufDst[16]; char bufSrc1[16]; char bufSrc2[16]; - DisassembleParam(bufDst, sizeof(bufDst) - 2, inst.dest, meta->types[0], constPool); - DisassembleParam(bufSrc1, sizeof(bufSrc1) - 2, inst.src1, meta->types[1], constPool); - DisassembleParam(bufSrc2, sizeof(bufSrc2), inst.src2, meta->types[2], constPool); + DisassembleParam(bufDst, sizeof(bufDst) - 2, inst.dest, meta->types[0], inst.constant); + DisassembleParam(bufSrc1, sizeof(bufSrc1) - 2, inst.src1, meta->types[1], inst.constant); + DisassembleParam(bufSrc2, sizeof(bufSrc2), inst.src2, meta->types[2], inst.constant); if (meta->types[1] && meta->types[0] != '_') { strcat(bufDst, ", "); } diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 50d9a6707d..cccc1ba55b 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -90,12 +90,16 @@ enum class IROp : u8 { Load16, Load16Ext, Load32, + Load32Left, + Load32Right, LoadFloat, LoadVec4, Store8, Store16, Store32, + Store32Left, + Store32Right, StoreFloat, StoreVec4, @@ -263,7 +267,7 @@ inline IROp ComparisonToExit(IRComparison comp) { enum IRFpCompareMode { False = 0, - NotEqualUnordered, + EitherUnordered, EqualOrdered, // eq, seq (equal, ordered) EqualUnordered, // ueq, ngl (equal, unordered) LessOrdered, // olt, lt (less than, ordered) @@ -275,8 +279,14 @@ enum IRFpCompareMode { enum { IRTEMP_0 = 192, IRTEMP_1, + IRTEMP_2, + IRTEMP_3, IRTEMP_LHS, // Reserved for use in branches IRTEMP_RHS, // Reserved for use in branches + IRTEMP_LR_ADDR, // Reserved for left/right loads and stores. + IRTEMP_LR_VALUE, // Reserved for left/right loads and stores. + IRTEMP_LR_MASK, // Reserved for left/right loads and stores. + IRTEMP_LR_SHIFT, // Reserved for left/right loads and stores. IRVTEMP_PFX_S = 224 - 32, // Relative to the FP regs IRVTEMP_PFX_T = 228 - 32, @@ -311,7 +321,7 @@ struct IRMeta { u32 flags; }; -// 32 bits. +// 64 bits. struct IRInst { IROp op; union { @@ -320,22 +330,21 @@ struct IRInst { }; u8 src1; u8 src2; + u32 constant; }; // Returns the new PC. -u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int count); +u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count); // Each IR block gets a constant pool. class IRWriter { public: IRWriter &operator =(const IRWriter &w) { insts_ = w.insts_; - constPool_ = w.constPool_; return *this; } IRWriter &operator =(IRWriter &&w) { insts_ = std::move(w.insts_); - constPool_ = std::move(w.constPool_); return *this; } @@ -350,17 +359,19 @@ public: void Clear() { insts_.clear(); - constPool_.clear(); } const std::vector &GetInstructions() const { return insts_; } - const std::vector &GetConstants() const { return constPool_; } private: std::vector insts_; - std::vector constPool_; + u32 nextConst_ = 0; +}; + +struct IROptions { + bool unalignedLoadStore; }; const IRMeta *GetIRMeta(IROp op); -void DisassembleIR(char *buf, size_t bufsize, IRInst inst, const u32 *constPool); +void DisassembleIR(char *buf, size_t bufsize, IRInst inst); void InitIR(); diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index f3a56ba51c..dee961f98e 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -1,6 +1,7 @@ #include #include +#include "ppsspp_config.h" #include "math/math_util.h" #include "Common/Common.h" @@ -8,6 +9,10 @@ #include #endif +#if PPSSPP_ARCH(ARM_NEON) +#include +#endif + #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/Debugger/Breakpoints.h" @@ -22,7 +27,7 @@ #include "Core/MIPS/IR/IRInterpreter.h" #include "Core/System.h" -alignas(16) float vec4InitValues[8][4] = { +alignas(16) static const float vec4InitValues[8][4] = { { 0.0f, 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { -1.0f, -1.0f, -1.0f, -1.0f }, @@ -32,6 +37,18 @@ alignas(16) float vec4InitValues[8][4] = { { 0.0f, 0.0f, 0.0f, 1.0f }, }; +alignas(16) static const uint32_t signBits[4] = { + 0x80000000, 0x80000000, 0x80000000, 0x80000000, +}; + +alignas(16) static const uint32_t noSignMask[4] = { + 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, +}; + +alignas(16) static const uint32_t lowBytesMask[4] = { + 0x000000FF, 0x000000FF, 0x000000FF, 0x000000FF, +}; + u32 RunBreakpoint(u32 pc) { // Should we skip this breakpoint? if (CBreakPoints::CheckSkipFirst() == pc) @@ -50,7 +67,8 @@ u32 RunMemCheck(u32 pc, u32 addr) { return coreState != CORE_RUNNING ? 1 : 0; } -u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int count) { +// We cannot use NEON on ARM32 here until we make it a hard dependency. We can, however, on ARM64. +u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { const IRInst *end = inst + count; while (inst != end) { switch (inst->op) { @@ -58,10 +76,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c _assert_(false); break; case IROp::SetConst: - mips->r[inst->dest] = constPool[inst->src1]; + mips->r[inst->dest] = inst->constant; break; case IROp::SetConstF: - memcpy(&mips->f[inst->dest], &constPool[inst->src1], 4); + memcpy(&mips->f[inst->dest], &inst->constant, 4); break; case IROp::Add: mips->r[inst->dest] = mips->r[inst->src1] + mips->r[inst->src2]; @@ -82,19 +100,19 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c mips->r[inst->dest] = mips->r[inst->src1]; break; case IROp::AddConst: - mips->r[inst->dest] = mips->r[inst->src1] + constPool[inst->src2]; + mips->r[inst->dest] = mips->r[inst->src1] + inst->constant; break; case IROp::SubConst: - mips->r[inst->dest] = mips->r[inst->src1] - constPool[inst->src2]; + mips->r[inst->dest] = mips->r[inst->src1] - inst->constant; break; case IROp::AndConst: - mips->r[inst->dest] = mips->r[inst->src1] & constPool[inst->src2]; + mips->r[inst->dest] = mips->r[inst->src1] & inst->constant; break; case IROp::OrConst: - mips->r[inst->dest] = mips->r[inst->src1] | constPool[inst->src2]; + mips->r[inst->dest] = mips->r[inst->src1] | inst->constant; break; case IROp::XorConst: - mips->r[inst->dest] = mips->r[inst->src1] ^ constPool[inst->src2]; + mips->r[inst->dest] = mips->r[inst->src1] ^ inst->constant; break; case IROp::Neg: mips->r[inst->dest] = -(s32)mips->r[inst->src1]; @@ -113,40 +131,78 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c break; case IROp::Load8: - mips->r[inst->dest] = Memory::ReadUnchecked_U8(mips->r[inst->src1] + constPool[inst->src2]); + mips->r[inst->dest] = Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant); break; case IROp::Load8Ext: - mips->r[inst->dest] = (s32)(s8)Memory::ReadUnchecked_U8(mips->r[inst->src1] + constPool[inst->src2]); + mips->r[inst->dest] = (s32)(s8)Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant); break; case IROp::Load16: - mips->r[inst->dest] = Memory::ReadUnchecked_U16(mips->r[inst->src1] + constPool[inst->src2]); + mips->r[inst->dest] = Memory::ReadUnchecked_U16(mips->r[inst->src1] + inst->constant); break; case IROp::Load16Ext: - mips->r[inst->dest] = (s32)(s16)Memory::ReadUnchecked_U16(mips->r[inst->src1] + constPool[inst->src2]); + mips->r[inst->dest] = (s32)(s16)Memory::ReadUnchecked_U16(mips->r[inst->src1] + inst->constant); break; case IROp::Load32: - mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + constPool[inst->src2]); + mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + inst->constant); break; + case IROp::Load32Left: + { + u32 addr = mips->r[inst->src1] + inst->constant; + u32 shift = (addr & 3) * 8; + u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); + u32 destMask = 0x00ffffff >> shift; + mips->r[inst->dest] = (mips->r[inst->dest] & destMask) | (mem << (24 - shift)); + break; + } + case IROp::Load32Right: + { + u32 addr = mips->r[inst->src1] + inst->constant; + u32 shift = (addr & 3) * 8; + u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); + u32 destMask = 0xffffff00 << (24 - shift); + mips->r[inst->dest] = (mips->r[inst->dest] & destMask) | (mem >> shift); + break; + } case IROp::LoadFloat: - mips->f[inst->dest] = Memory::ReadUnchecked_Float(mips->r[inst->src1] + constPool[inst->src2]); + mips->f[inst->dest] = Memory::ReadUnchecked_Float(mips->r[inst->src1] + inst->constant); break; case IROp::Store8: - Memory::WriteUnchecked_U8(mips->r[inst->src3], mips->r[inst->src1] + constPool[inst->src2]); + Memory::WriteUnchecked_U8(mips->r[inst->src3], mips->r[inst->src1] + inst->constant); break; case IROp::Store16: - Memory::WriteUnchecked_U16(mips->r[inst->src3], mips->r[inst->src1] + constPool[inst->src2]); + Memory::WriteUnchecked_U16(mips->r[inst->src3], mips->r[inst->src1] + inst->constant); break; case IROp::Store32: - Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + constPool[inst->src2]); + Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + inst->constant); break; + case IROp::Store32Left: + { + u32 addr = mips->r[inst->src1] + inst->constant; + u32 shift = (addr & 3) * 8; + u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); + u32 memMask = 0xffffff00 << shift; + u32 result = (mips->r[inst->src3] >> (24 - shift)) | (mem & memMask); + Memory::WriteUnchecked_U32(result, addr & 0xfffffffc); + break; + } + case IROp::Store32Right: + { + u32 addr = mips->r[inst->src1] + inst->constant; + u32 shift = (addr & 3) * 8; + u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); + u32 memMask = 0x00ffffff >> (24 - shift); + u32 result = (mips->r[inst->src3] << shift) | (mem & memMask); + Memory::WriteUnchecked_U32(result, addr & 0xfffffffc); + break; + } case IROp::StoreFloat: - Memory::WriteUnchecked_Float(mips->f[inst->src3], mips->r[inst->src1] + constPool[inst->src2]); + Memory::WriteUnchecked_Float(mips->f[inst->src3], mips->r[inst->src1] + inst->constant); break; case IROp::LoadVec4: { - u32 base = mips->r[inst->src1] + constPool[inst->src2]; + u32 base = mips->r[inst->src1] + inst->constant; #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_load_ps((const float *)Memory::GetPointerUnchecked(base))); #else @@ -157,7 +213,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c } case IROp::StoreVec4: { - u32 base = mips->r[inst->src1] + constPool[inst->src2]; + u32 base = mips->r[inst->src1] + inst->constant; #if defined(_M_SSE) _mm_store_ps((float *)Memory::GetPointerUnchecked(base), _mm_load_ps(&mips->f[inst->dest])); #else @@ -168,58 +224,77 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c } case IROp::Vec4Init: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_load_ps(vec4InitValues[inst->src1])); #else memcpy(&mips->f[inst->dest], vec4InitValues[inst->src1], 4 * sizeof(float)); #endif break; + } case IROp::Vec4Shuffle: { - // Can't use the SSE shuffle here because it takes an immediate. - // Backends with SSE support could use that though. + // Can't use the SSE shuffle here because it takes an immediate. pshufb with a table would work though, + // or a big switch - there are only 256 shuffles possible (4^4) for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + ((inst->src2 >> (i * 2)) & 3)]; break; } case IROp::Vec4Mov: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_load_ps(&mips->f[inst->src1])); +#elif PPSSPP_ARCH(ARM64) + vst1q_f32(&mips->f[inst->dest], vld1q_f32(&mips->f[inst->src1])); #else memcpy(&mips->f[inst->dest], &mips->f[inst->src1], 4 * sizeof(float)); #endif break; + } case IROp::Vec4Add: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_add_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2]))); +#elif PPSSPP_ARCH(ARM64) + vst1q_f32(&mips->f[inst->dest], vaddq_f32(vld1q_f32(&mips->f[inst->src1]), vld1q_f32(&mips->f[inst->src2]))); #else for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + i] + mips->f[inst->src2 + i]; #endif break; + } case IROp::Vec4Sub: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_sub_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2]))); +#elif PPSSPP_ARCH(ARM64) + vst1q_f32(&mips->f[inst->dest], vsubq_f32(vld1q_f32(&mips->f[inst->src1]), vld1q_f32(&mips->f[inst->src2]))); #else for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + i] - mips->f[inst->src2 + i]; #endif break; + } case IROp::Vec4Mul: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_mul_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2]))); +#elif PPSSPP_ARCH(ARM64) + vst1q_f32(&mips->f[inst->dest], vmulq_f32(vld1q_f32(&mips->f[inst->src1]), vld1q_f32(&mips->f[inst->src2]))); #else for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + i] * mips->f[inst->src2 + i]; #endif break; + } case IROp::Vec4Div: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_div_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2]))); #else @@ -227,8 +302,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c mips->f[inst->dest + i] = mips->f[inst->src1 + i] / mips->f[inst->src2 + i]; #endif break; + } case IROp::Vec4Scale: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_mul_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_set1_ps(mips->f[inst->src2]))); #else @@ -236,33 +313,63 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c mips->f[inst->dest + i] = mips->f[inst->src1 + i] * mips->f[inst->src2]; #endif break; + } case IROp::Vec4Neg: + { +#if defined(_M_SSE) + _mm_store_ps(&mips->f[inst->dest], _mm_xor_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps((const float *)signBits))); +#elif PPSSPP_ARCH(ARM64) + vst1q_f32(&mips->f[inst->dest], vnegq_f32(vld1q_f32(&mips->f[inst->src1]))); +#else for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = -mips->f[inst->src1 + i]; +#endif break; + } case IROp::Vec4Abs: + { +#if defined(_M_SSE) + _mm_store_ps(&mips->f[inst->dest], _mm_and_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps((const float *)noSignMask))); +#elif PPSSPP_ARCH(ARM64) + vst1q_f32(&mips->f[inst->dest], vabsq_f32(vld1q_f32(&mips->f[inst->src1]))); +#else for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = fabsf(mips->f[inst->src1 + i]); +#endif break; + } case IROp::Vec2Unpack16To31: + { mips->fi[inst->dest] = (mips->fi[inst->src1] << 16) >> 1; mips->fi[inst->dest + 1] = (mips->fi[inst->src1] & 0xFFFF0000) >> 1; break; + } case IROp::Vec2Unpack16To32: + { mips->fi[inst->dest] = (mips->fi[inst->src1] << 16); mips->fi[inst->dest + 1] = (mips->fi[inst->src1] & 0xFFFF0000); break; + } case IROp::Vec4Unpack8To32: + { +#if defined(_M_SSE) + __m128i src = _mm_cvtsi32_si128(mips->fi[inst->src1]); + src = _mm_unpacklo_epi8(src, _mm_setzero_si128()); + src = _mm_unpacklo_epi16(src, _mm_setzero_si128()); + _mm_store_si128((__m128i *)&mips->fi[inst->dest], _mm_slli_epi32(src, 24)); +#else mips->fi[inst->dest] = (mips->fi[inst->src1] << 24); mips->fi[inst->dest + 1] = (mips->fi[inst->src1] << 16) & 0xFF000000; mips->fi[inst->dest + 2] = (mips->fi[inst->src1] << 8) & 0xFF000000; mips->fi[inst->dest + 3] = (mips->fi[inst->src1]) & 0xFF000000; +#endif break; + } case IROp::Vec2Pack32To16: { @@ -281,6 +388,8 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c case IROp::Vec4Pack32To8: { + // Removed previous SSE code due to the need for unsigned 16-bit pack, which I'm too lazy to work around the lack of in SSE2. + // pshufb or SSE4 instructions can be used instead. u32 val = mips->fi[inst->src1] >> 24; val |= (mips->fi[inst->src1 + 1] >> 16) & 0xFF00; val |= (mips->fi[inst->src1 + 2] >> 8) & 0xFF0000; @@ -291,6 +400,8 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c case IROp::Vec4Pack31To8: { + // Removed previous SSE code due to the need for unsigned 16-bit pack, which I'm too lazy to work around the lack of in SSE2. + // pshufb or SSE4 instructions can be used instead. u32 val = (mips->fi[inst->src1] >> 23) & 0xFF; val |= (mips->fi[inst->src1 + 1] >> 15) & 0xFF00; val |= (mips->fi[inst->src1 + 2] >> 7) & 0xFF0000; @@ -310,14 +421,23 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c case IROp::Vec4ClampToZero: { +#if defined(_M_SSE) + // Trickery: Expand the sign bit, and use andnot to zero negative values. + __m128i val = _mm_load_si128((const __m128i *)&mips->fi[inst->src1]); + __m128i mask = _mm_srai_epi32(val, 31); + val = _mm_andnot_si128(mask, val); + _mm_store_si128((__m128i *)&mips->fi[inst->dest], val); +#else for (int i = 0; i < 4; i++) { u32 val = mips->fi[inst->src1 + i]; mips->fi[inst->dest + i] = (int)val >= 0 ? val : 0; } +#endif break; } - case IROp::Vec4DuplicateUpperBitsAndShift1: + case IROp::Vec4DuplicateUpperBitsAndShift1: // For vuc2i, the weird one. + { for (int i = 0; i < 4; i++) { u32 val = mips->fi[inst->src1 + i]; val = val | (val >> 8); @@ -326,6 +446,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c mips->fi[inst->dest + i] = val; } break; + } case IROp::FCmpVfpuBit: { @@ -357,18 +478,18 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c } else { mips->vfpuCtrl[VFPU_CTRL_CC] &= ~(1 << bit); } - } break; + } case IROp::FCmpVfpuAggregate: { u32 mask = inst->dest; u32 cc = mips->vfpuCtrl[VFPU_CTRL_CC]; - int a = (cc & mask) ? 0x10 : 0x00; - int b = (cc & mask) == mask ? 0x20 : 0x00; - mips->vfpuCtrl[VFPU_CTRL_CC] = (cc & ~0x30) | a | b; - } + int anyBit = (cc & mask) ? 0x10 : 0x00; + int allBit = (cc & mask) == mask ? 0x20 : 0x00; + mips->vfpuCtrl[VFPU_CTRL_CC] = (cc & ~0x30) | anyBit | allBit; break; + } case IROp::FCmovVfpuCC: if (((mips->vfpuCtrl[VFPU_CTRL_CC] >> (inst->src2 & 0xf)) & 1) == ((u32)inst->src2 >> 7)) { @@ -433,8 +554,8 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c u32 x = mips->r[inst->src1]; int sa = mips->r[inst->src2] & 31; mips->r[inst->dest] = (x >> sa) | (x << (32 - sa)); + break; } - break; case IROp::Clz: { @@ -458,11 +579,11 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c break; case IROp::SltConst: - mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)constPool[inst->src2]; + mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)inst->constant; break; case IROp::SltUConst: - mips->r[inst->dest] = mips->r[inst->src1] < constPool[inst->src2]; + mips->r[inst->dest] = mips->r[inst->src1] < inst->constant; break; case IROp::MovZ: @@ -632,6 +753,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c mips->f[inst->dest] = 1.0f; else mips->f[inst->dest] = -1.0f; + break; } case IROp::FpCondToReg: @@ -641,34 +763,70 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c mips->r[inst->dest] = mips->vfpuCtrl[inst->src1]; break; case IROp::FRound: - mips->fs[inst->dest] = (int)floorf(mips->f[inst->src1] + 0.5f); - break; - case IROp::FTrunc: { - float src = mips->f[inst->src1]; - if (src >= 0.0f) { - mips->fs[inst->dest] = (int)floorf(src); - // Overflow, but it was positive. - if (mips->fs[inst->dest] == -2147483648LL) { - mips->fs[inst->dest] = 2147483647LL; - } + float value = mips->f[inst->src1]; + if (my_isnanorinf(value)) { + mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL; + break; } else { - // Overflow happens to be the right value anyway. - mips->fs[inst->dest] = (int)ceilf(src); + mips->fs[inst->dest] = (int)floorf(value + 0.5f); } break; } + case IROp::FTrunc: + { + float value = mips->f[inst->src1]; + if (my_isnanorinf(value)) { + mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL; + break; + } else { + if (value >= 0.0f) { + mips->fs[inst->dest] = (int)floorf(value); + // Overflow, but it was positive. + if (mips->fs[inst->dest] == -2147483648LL) { + mips->fs[inst->dest] = 2147483647LL; + } + } else { + // Overflow happens to be the right value anyway. + mips->fs[inst->dest] = (int)ceilf(value); + } + break; + } + } case IROp::FCeil: - mips->fs[inst->dest] = (int)ceilf(mips->f[inst->src1]); + { + float value = mips->f[inst->src1]; + if (my_isnanorinf(value)) { + mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL; + break; + } else { + mips->fs[inst->dest] = (int)ceilf(value); + } break; + } case IROp::FFloor: - mips->fs[inst->dest] = (int)floorf(mips->f[inst->src1]); + { + float value = mips->f[inst->src1]; + if (my_isnanorinf(value)) { + mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL; + break; + } else { + mips->fs[inst->dest] = (int)floorf(value); + } break; + } case IROp::FCmp: switch (inst->dest) { case IRFpCompareMode::False: mips->fpcond = 0; break; + case IRFpCompareMode::EitherUnordered: + { + float a = mips->f[inst->src1]; + float b = mips->f[inst->src2]; + mips->fpcond = !(a > b || a < b || a == b); + break; + } case IRFpCompareMode::EqualOrdered: case IRFpCompareMode::EqualUnordered: mips->fpcond = mips->f[inst->src1] == mips->f[inst->src2]; @@ -690,13 +848,11 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c case IROp::FCvtWS: { float src = mips->f[inst->src1]; - if (my_isnanorinf(src)) - { + if (my_isnanorinf(src)) { mips->fs[inst->dest] = my_isinf(src) && src < 0.0f ? -2147483648LL : 2147483647LL; break; } - switch (mips->fcr31 & 3) - { + switch (mips->fcr31 & 3) { case 0: mips->fs[inst->dest] = (int)round_ieee_754(src); break; // RINT_0 case 1: mips->fs[inst->dest] = (int)src; break; // CAST_1 case 2: mips->fs[inst->dest] = (int)ceilf(src); break; // CEIL_2 @@ -717,38 +873,38 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c break; case IROp::ExitToConst: - return constPool[inst->dest]; + return inst->constant; case IROp::ExitToReg: return mips->r[inst->src1]; case IROp::ExitToConstIfEq: if (mips->r[inst->src1] == mips->r[inst->src2]) - return constPool[inst->dest]; + return inst->constant; break; case IROp::ExitToConstIfNeq: if (mips->r[inst->src1] != mips->r[inst->src2]) - return constPool[inst->dest]; + return inst->constant; break; case IROp::ExitToConstIfGtZ: if ((s32)mips->r[inst->src1] > 0) - return constPool[inst->dest]; + return inst->constant; break; case IROp::ExitToConstIfGeZ: if ((s32)mips->r[inst->src1] >= 0) - return constPool[inst->dest]; + return inst->constant; break; case IROp::ExitToConstIfLtZ: if ((s32)mips->r[inst->src1] < 0) - return constPool[inst->dest]; + return inst->constant; break; case IROp::ExitToConstIfLeZ: if ((s32)mips->r[inst->src1] <= 0) - return constPool[inst->dest]; + return inst->constant; break; case IROp::Downcount: - mips->downcount -= (inst->src1) | ((inst->src2) << 8); + mips->downcount -= inst->constant; break; case IROp::SetPC: @@ -756,13 +912,13 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c break; case IROp::SetPCConst: - mips->pc = constPool[inst->src1]; + mips->pc = inst->constant; break; case IROp::Syscall: - // SetPC was executed before. + // IROp::SetPC was (hopefully) executed before. { - MIPSOpcode op(constPool[inst->src1]); + MIPSOpcode op(inst->constant); CallSyscall(op); if (coreState != CORE_RUNNING) CoreTiming::ForceCheck(); @@ -772,16 +928,16 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c case IROp::ExitToPC: return mips->pc; - case IROp::Interpret: // SLOW fallback. Can be made faster. + case IROp::Interpret: // SLOW fallback. Can be made faster. Ideally should be removed but may be useful for debugging. { - MIPSOpcode op(constPool[inst->src1]); + MIPSOpcode op(inst->constant); MIPSInterpret(op); break; } case IROp::CallReplacement: { - int funcIndex = constPool[inst->src1]; + int funcIndex = inst->constant; const ReplacementTableEntry *f = GetReplacementFunc(funcIndex); int cycles = f->replaceFunc(); mips->downcount -= cycles; @@ -796,7 +952,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c return mips->pc + 4; case IROp::SetCtrlVFPU: - mips->vfpuCtrl[inst->dest] = constPool[inst->src1]; + mips->vfpuCtrl[inst->dest] = inst->constant; break; case IROp::SetCtrlVFPUReg: @@ -815,7 +971,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c break; case IROp::MemoryCheck: - if (RunMemCheck(mips->pc, mips->r[inst->src1] + constPool[inst->src2])) { + if (RunMemCheck(mips->pc, mips->r[inst->src1] + inst->constant)) { CoreTiming::ForceCheck(); return mips->pc; } diff --git a/Core/MIPS/IR/IRInterpreter.h b/Core/MIPS/IR/IRInterpreter.h index 562153673a..092c2ac936 100644 --- a/Core/MIPS/IR/IRInterpreter.h +++ b/Core/MIPS/IR/IRInterpreter.h @@ -20,4 +20,4 @@ inline static u32 ReverseBits32(u32 v) { return v; } -u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int count); +u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count); diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index b36c40f110..9a86a4356f 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "base/logging.h" +#include "ext/xxhash.h" #include "profiler/profiler.h" #include "Common/ChunkFile.h" #include "Common/StringUtils.h" @@ -41,6 +42,10 @@ IRJit::IRJit(MIPSState *mips) : frontend_(mips->HasDefaultPrefix()), mips_(mips) u32 size = 128 * 1024; // blTrampolines_ = kernelMemory.Alloc(size, true, "trampoline"); InitIR(); + + IROptions opts{}; + opts.unalignedLoadStore = true; + frontend_.SetOptions(opts); } IRJit::~IRJit() { @@ -62,21 +67,136 @@ void IRJit::InvalidateCacheAt(u32 em_address, int length) { void IRJit::Compile(u32 em_address) { PROFILE_THIS_SCOPE("jitc"); - int block_num = blocks_.AllocateBlock(em_address); - IRBlock *b = blocks_.GetBlock(block_num); + if (g_Config.bPreloadFunctions) { + // Look to see if we've preloaded this block. + int block_num = blocks_.FindPreloadBlock(em_address); + if (block_num != -1) { + IRBlock *b = blocks_.GetBlock(block_num); + // Okay, let's link and finalize the block now. + b->Finalize(block_num); + if (b->IsValid()) { + // Success, we're done. + return; + } + } + } std::vector instructions; - std::vector constants; u32 mipsBytes; - frontend_.DoJit(em_address, instructions, constants, mipsBytes); - b->SetInstructions(instructions, constants); - b->SetOriginalSize(mipsBytes); - b->Finalize(block_num); // Overwrites the first instruction + if (!CompileBlock(em_address, instructions, mipsBytes, false)) { + // Ran out of block numbers - need to reset. + ERROR_LOG(JIT, "Ran out of block numbers, clearing cache"); + ClearCache(); + CompileBlock(em_address, instructions, mipsBytes, false); + } - if (frontend_.CheckRounding()) { + if (frontend_.CheckRounding(em_address)) { // Our assumptions are all wrong so it's clean-slate time. ClearCache(); - Compile(em_address); + CompileBlock(em_address, instructions, mipsBytes, false); + } +} + +bool IRJit::CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload) { + frontend_.DoJit(em_address, instructions, mipsBytes, preload); + if (instructions.empty()) { + _dbg_assert_(JIT, preload); + // We return true when preloading so it doesn't abort. + return preload; + } + + int block_num = blocks_.AllocateBlock(em_address); + if ((block_num & ~MIPS_EMUHACK_VALUE_MASK) != 0) { + // Out of block numbers. Caller will handle. + return false; + } + + IRBlock *b = blocks_.GetBlock(block_num); + b->SetInstructions(instructions); + b->SetOriginalSize(mipsBytes); + if (preload) { + // Hash, then only update page stats, don't link yet. + b->UpdateHash(); + blocks_.FinalizeBlock(block_num, true); + } else { + // Overwrites the first instruction, and also updates stats. + // TODO: Should we always hash? Then we can reuse blocks. + blocks_.FinalizeBlock(block_num); + } + + return true; +} + +void IRJit::CompileFunction(u32 start_address, u32 length) { + PROFILE_THIS_SCOPE("jitc"); + + // Note: we don't actually write emuhacks yet, so we can validate hashes. + // This way, if the game changes the code afterward, we'll catch even without icache invalidation. + + // We may go up and down from branches, so track all block starts done here. + std::set doneAddresses; + std::vector pendingAddresses; + pendingAddresses.push_back(start_address); + while (!pendingAddresses.empty()) { + u32 em_address = pendingAddresses.back(); + pendingAddresses.pop_back(); + + // To be safe, also check if a real block is there. This can be a runtime module load. + u32 inst = Memory::ReadUnchecked_U32(em_address); + if (MIPS_IS_RUNBLOCK(inst) || doneAddresses.find(em_address) != doneAddresses.end()) { + // Already compiled this address. + continue; + } + + std::vector instructions; + u32 mipsBytes; + if (!CompileBlock(em_address, instructions, mipsBytes, true)) { + // Ran out of block numbers - let's hope there's no more code it needs to run. + // Will flush when actually compiling. + ERROR_LOG(JIT, "Ran out of block numbers while compiling function"); + return; + } + + doneAddresses.insert(em_address); + + for (const IRInst &inst : instructions) { + u32 exit = 0; + + switch (inst.op) { + case IROp::ExitToConst: + case IROp::ExitToConstIfEq: + case IROp::ExitToConstIfNeq: + case IROp::ExitToConstIfGtZ: + case IROp::ExitToConstIfGeZ: + case IROp::ExitToConstIfLtZ: + case IROp::ExitToConstIfLeZ: + case IROp::ExitToConstIfFpTrue: + case IROp::ExitToConstIfFpFalse: + exit = inst.constant; + break; + + case IROp::ExitToPC: + case IROp::Break: + // Don't add any, we'll do block end anyway (for jal, etc.) + exit = 0; + break; + + default: + exit = 0; + break; + } + + // Only follow jumps internal to the function. + if (exit != 0 && exit >= start_address && exit < start_address + length) { + // Even if it's a duplicate, we check at loop start. + pendingAddresses.push_back(exit); + } + } + + // Also include after the block for jal returns. + if (em_address + mipsBytes < start_address + length) { + pendingAddresses.push_back(em_address + mipsBytes); + } } } @@ -99,7 +219,7 @@ void IRJit::RunLoopUntil(u64 globalticks) { if (opcode == MIPS_EMUHACK_OPCODE) { u32 data = inst & 0xFFFFFF; IRBlock *block = blocks_.GetBlock(data); - mips_->pc = IRInterpret(mips_, block->GetInstructions(), block->GetConstants(), block->GetNumInstructions()); + mips_->pc = IRInterpret(mips_, block->GetInstructions(), block->GetNumInstructions()); } else { // RestoreRoundingMode(true); Compile(mips_->pc); @@ -112,7 +232,7 @@ void IRJit::RunLoopUntil(u64 globalticks) { } bool IRJit::DescribeCodePtr(const u8 *ptr, std::string &name) { - // Used in disassembly viewer. + // Used in target disassembly viewer. return false; } @@ -130,26 +250,79 @@ bool IRJit::ReplaceJalTo(u32 dest) { } void IRBlockCache::Clear() { - for (int i = 0; i < size_; ++i) { + for (int i = 0; i < (int)blocks_.size(); ++i) { blocks_[i].Destroy(i); } blocks_.clear(); + byPage_.clear(); } void IRBlockCache::InvalidateICache(u32 address, u32 length) { - // TODO: Could be more efficient. - for (int i = 0; i < size_; ++i) { - if (blocks_[i].OverlapsRange(address, length)) { - blocks_[i].Destroy(i); + u32 startPage = AddressToPage(address); + u32 endPage = AddressToPage(address + length); + + for (u32 page = startPage; page <= endPage; ++page) { + const auto iter = byPage_.find(page); + if (iter == byPage_.end()) + continue; + + const std::vector &blocksInPage = iter->second; + for (int i : blocksInPage) { + if (blocks_[i].OverlapsRange(address, length)) { + // Not removing from the page, hopefully doesn't build up with small recompiles. + blocks_[i].Destroy(i); + } } } } +void IRBlockCache::FinalizeBlock(int i, bool preload) { + if (!preload) { + blocks_[i].Finalize(i); + } + + u32 startAddr, size; + blocks_[i].GetRange(startAddr, size); + + u32 startPage = AddressToPage(startAddr); + u32 endPage = AddressToPage(startAddr + size); + + for (u32 page = startPage; page <= endPage; ++page) { + byPage_[page].push_back(i); + } +} + +u32 IRBlockCache::AddressToPage(u32 addr) const { + // Use relatively small pages since basic blocks are typically small. + return (addr & 0x3FFFFFFF) >> 10; +} + +int IRBlockCache::FindPreloadBlock(u32 em_address) { + u32 page = AddressToPage(em_address); + auto iter = byPage_.find(page); + if (iter == byPage_.end()) + return -1; + + const std::vector &blocksInPage = iter->second; + for (int i : blocksInPage) { + u32 start, mipsBytes; + blocks_[i].GetRange(start, mipsBytes); + + if (start == em_address) { + if (blocks_[i].HashMatches()) { + return i; + } + } + } + + return -1; +} + std::vector IRBlockCache::SaveAndClearEmuHackOps() { std::vector result; - result.resize(size_); + result.resize(blocks_.size()); - for (int number = 0; number < size_; ++number) { + for (int number = 0; number < (int)blocks_.size(); ++number) { IRBlock &b = blocks_[number]; if (b.IsValid() && b.RestoreOriginalFirstOp(number)) { result[number] = number; @@ -162,12 +335,12 @@ std::vector IRBlockCache::SaveAndClearEmuHackOps() { } void IRBlockCache::RestoreSavedEmuHackOps(std::vector saved) { - if (size_ != (int)saved.size()) { + if ((int)blocks_.size() != (int)saved.size()) { ERROR_LOG(JIT, "RestoreSavedEmuHackOps: Wrong saved block size."); return; } - for (int number = 0; number < size_; ++number) { + for (int number = 0; number < (int)blocks_.size(); ++number) { IRBlock &b = blocks_[number]; // Only if we restored it, write it back. if (b.IsValid() && saved[number] != 0 && b.HasOriginalFirstOp()) { @@ -176,7 +349,82 @@ void IRBlockCache::RestoreSavedEmuHackOps(std::vector saved) { } } -bool IRBlock::HasOriginalFirstOp() { +JitBlockDebugInfo IRBlockCache::GetBlockDebugInfo(int blockNum) const { + const IRBlock &ir = blocks_[blockNum]; + JitBlockDebugInfo debugInfo{}; + uint32_t start, size; + ir.GetRange(start, size); + debugInfo.originalAddress = start; // TODO + + for (u32 addr = start; addr < start + size; addr += 4) { + char temp[256]; + MIPSDisAsm(Memory::Read_Instruction(addr), addr, temp, true); + std::string mipsDis = temp; + debugInfo.origDisasm.push_back(mipsDis); + } + + for (int i = 0; i < ir.GetNumInstructions(); i++) { + IRInst inst = ir.GetInstructions()[i]; + char buffer[256]; + DisassembleIR(buffer, sizeof(buffer), inst); + debugInfo.irDisasm.push_back(buffer); + } + return debugInfo; +} + +void IRBlockCache::ComputeStats(BlockCacheStats &bcStats) const { + double totalBloat = 0.0; + double maxBloat = 0.0; + double minBloat = 1000000000.0; + for (const auto &b : blocks_) { + double codeSize = (double)b.GetNumInstructions() * sizeof(IRInst); + if (codeSize == 0) + continue; + + u32 origAddr, mipsBytes; + b.GetRange(origAddr, mipsBytes); + double origSize = (double)mipsBytes; + double bloat = codeSize / origSize; + if (bloat < minBloat) { + minBloat = bloat; + bcStats.minBloatBlock = origAddr; + } + if (bloat > maxBloat) { + maxBloat = bloat; + bcStats.maxBloatBlock = origAddr; + } + totalBloat += bloat; + bcStats.bloatMap[bloat] = origAddr; + } + bcStats.numBlocks = (int)blocks_.size(); + bcStats.minBloat = minBloat; + bcStats.maxBloat = maxBloat; + bcStats.avgBloat = totalBloat / (double)blocks_.size(); +} + +int IRBlockCache::GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly) const { + u32 page = AddressToPage(em_address); + + const auto iter = byPage_.find(page); + if (iter == byPage_.end()) + return -1; + + const std::vector &blocksInPage = iter->second; + int best = -1; + for (int i : blocksInPage) { + uint32_t start, size; + blocks_[i].GetRange(start, size); + if (start == em_address) { + best = i; + if (blocks_[i].IsValid()) { + return i; + } + } + } + return best; +} + +bool IRBlock::HasOriginalFirstOp() const { return Memory::ReadUnchecked_U32(origAddr_) == origFirstOpcode_.encoding; } @@ -190,9 +438,13 @@ bool IRBlock::RestoreOriginalFirstOp(int number) { } void IRBlock::Finalize(int number) { - origFirstOpcode_ = Memory::Read_Opcode_JIT(origAddr_); - MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | number); - Memory::Write_Opcode_JIT(origAddr_, opcode); + // Check it wasn't invalidated, in case this is after preload. + // TODO: Allow reusing blocks when the code matches hash_ again, instead. + if (origAddr_) { + origFirstOpcode_ = Memory::Read_Opcode_JIT(origAddr_); + MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | number); + Memory::Write_Opcode_JIT(origAddr_, opcode); + } } void IRBlock::Destroy(int number) { @@ -206,8 +458,28 @@ void IRBlock::Destroy(int number) { } } -bool IRBlock::OverlapsRange(u32 addr, u32 size) { - return addr + size > origAddr_ && addr < origAddr_ + origSize_; +u64 IRBlock::CalculateHash() const { + if (origAddr_) { + // This is unfortunate. In case of emuhacks, we have to make a copy. + std::vector buffer; + buffer.resize(origSize_ / 4); + size_t pos = 0; + for (u32 off = 0; off < origSize_; off += 4) { + // Let's actually hash the replacement, if any. + MIPSOpcode instr = Memory::ReadUnchecked_Instruction(origAddr_ + off, false); + buffer[pos++] = instr.encoding; + } + + return XXH64(&buffer[0], origSize_, 0x9A5C33B8); + } + + return 0; +} + +bool IRBlock::OverlapsRange(u32 addr, u32 size) const { + addr &= 0x3FFFFFFF; + u32 origAddr = origAddr_ & 0x3FFFFFFF; + return addr + size > origAddr && addr < origAddr + origSize_; } MIPSOpcode IRJit::GetOriginalOp(MIPSOpcode op) { diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h index 67fa3a993c..beefcf9bcb 100644 --- a/Core/MIPS/IR/IRJit.h +++ b/Core/MIPS/IR/IRJit.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include "Common/Common.h" #include "Common/CPUDetect.h" @@ -37,88 +38,99 @@ namespace MIPSComp { // TODO : Use arena allocators. For now let's just malloc. class IRBlock { public: - IRBlock() : instr_(nullptr), const_(nullptr), numInstructions_(0), numConstants_(0), origAddr_(0), origSize_(0) {} - IRBlock(u32 emAddr) : instr_(nullptr), const_(nullptr), numInstructions_(0), numConstants_(0), origAddr_(emAddr), origSize_(0) {} + IRBlock() : instr_(nullptr), numInstructions_(0), origAddr_(0), origSize_(0) {} + IRBlock(u32 emAddr) : instr_(nullptr), numInstructions_(0), origAddr_(emAddr), origSize_(0) {} IRBlock(IRBlock &&b) { instr_ = b.instr_; - const_ = b.const_; numInstructions_ = b.numInstructions_; - numConstants_ = b.numConstants_; origAddr_ = b.origAddr_; origSize_ = b.origSize_; origFirstOpcode_ = b.origFirstOpcode_; + hash_ = b.hash_; b.instr_ = nullptr; - b.const_ = nullptr; } ~IRBlock() { delete[] instr_; - delete[] const_; } - void SetInstructions(const std::vector &inst, const std::vector &constants) { + void SetInstructions(const std::vector &inst) { instr_ = new IRInst[inst.size()]; numInstructions_ = (u16)inst.size(); if (!inst.empty()) { memcpy(instr_, &inst[0], sizeof(IRInst) * inst.size()); } - const_ = new u32[constants.size()]; - numConstants_ = (u16)constants.size(); - if (!constants.empty()) { - memcpy(const_, &constants[0], sizeof(u32) * constants.size()); - } } const IRInst *GetInstructions() const { return instr_; } - const u32 *GetConstants() const { return const_; } int GetNumInstructions() const { return numInstructions_; } MIPSOpcode GetOriginalFirstOp() const { return origFirstOpcode_; } - bool HasOriginalFirstOp(); + bool HasOriginalFirstOp() const; bool RestoreOriginalFirstOp(int number); - bool IsValid() const { return origAddr_ != 0; } + bool IsValid() const { return origAddr_ != 0 && origFirstOpcode_.encoding != 0x68FFFFFF; } void SetOriginalSize(u32 size) { origSize_ = size; } - bool OverlapsRange(u32 addr, u32 size); + void UpdateHash() { + hash_ = CalculateHash(); + } + bool HashMatches() const { + return origAddr_ && hash_ == CalculateHash(); + } + bool OverlapsRange(u32 addr, u32 size) const; + + void GetRange(u32 &start, u32 &size) const { + start = origAddr_; + size = origSize_; + } void Finalize(int number); void Destroy(int number); private: + u64 CalculateHash() const; + IRInst *instr_; - u32 *const_; u16 numInstructions_; - u16 numConstants_; u32 origAddr_; u32 origSize_; - MIPSOpcode origFirstOpcode_; + u64 hash_ = 0; + MIPSOpcode origFirstOpcode_ = MIPSOpcode(0x68FFFFFF); }; -class IRBlockCache { +class IRBlockCache : public JitBlockCacheDebugInterface { public: - IRBlockCache() : size_(0) {} + IRBlockCache() {} void Clear(); void InvalidateICache(u32 address, u32 length); - int GetNumBlocks() const { return (int)blocks_.size(); } + void FinalizeBlock(int i, bool preload = false); + int GetNumBlocks() const override { return (int)blocks_.size(); } int AllocateBlock(int emAddr) { blocks_.push_back(IRBlock(emAddr)); - size_ = (int)blocks_.size(); return (int)blocks_.size() - 1; } IRBlock *GetBlock(int i) { - if (i >= 0 && i < size_) { + if (i >= 0 && i < (int)blocks_.size()) { return &blocks_[i]; } else { return nullptr; } } + int FindPreloadBlock(u32 em_address); + std::vector SaveAndClearEmuHackOps(); void RestoreSavedEmuHackOps(std::vector saved); + JitBlockDebugInfo GetBlockDebugInfo(int blockNum) const override; + void ComputeStats(BlockCacheStats &bcStats) const override; + int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true) const override; + private: - int size_; // Hm, is this a cache for speed in debug mode, or what? + u32 AddressToPage(u32 addr) const; + std::vector blocks_; + std::unordered_map> byPage_; }; class IRJit : public JitInterface { @@ -133,10 +145,12 @@ public: void RunLoopUntil(u64 globalticks) override; void Compile(u32 em_address) override; // Compiles a block at current MIPS PC + void CompileFunction(u32 start_address, u32 length) override; bool DescribeCodePtr(const u8 *ptr, std::string &name) override; // Not using a regular block cache. JitBlockCache *GetBlockCache() override { return nullptr; } + JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks_; } MIPSOpcode GetOriginalOp(MIPSOpcode op) override; std::vector SaveAndClearEmuHackOps() override { return blocks_.SaveAndClearEmuHackOps(); } @@ -151,6 +165,7 @@ public: void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override; private: + bool CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload); bool ReplaceJalTo(u32 dest); JitOptions jo; diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index aba843689b..fa7481aeff 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -7,43 +7,6 @@ #include "Core/MIPS/IR/IRPassSimplify.h" #include "Core/MIPS/IR/IRRegCache.h" -void WriteInstWithConstants(const IRWriter &in, IRWriter &out, const u32 *constants, IRInst inst) { - // Remap constants to the new reality - const IRMeta *m = GetIRMeta(inst.op); - if (!m) { - ERROR_LOG(CPU, "Bad IR instruction %02x", (int)inst.op); - return; - } - switch (m->types[0]) { - case 'C': - if (!constants) { - ERROR_LOG(CPU, "Missing constant for type 0"); - return; - } - inst.dest = out.AddConstant(constants[inst.dest]); - break; - } - switch (m->types[1]) { - case 'C': - if (!constants) { - ERROR_LOG(CPU, "Missing constants for type 1"); - return; - } - inst.src1 = out.AddConstant(constants[inst.src1]); - break; - } - switch (m->types[2]) { - case 'C': - if (!constants) { - ERROR_LOG(CPU, "Missing constants for type 2"); - return; - } - inst.src2 = out.AddConstant(constants[inst.src2]); - break; - } - out.Write(inst); -} - u32 Evaluate(u32 a, u32 b, IROp op) { switch (op) { case IROp::Add: case IROp::AddConst: return a + b; @@ -110,9 +73,9 @@ IROp ShiftToShiftImm(IROp op) { } } -bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWriter &out) { +bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWriter &out, const IROptions &opts) { if (c == 1) { - return passes[0](in, out); + return passes[0](in, out, opts); } bool logBlocks = false; @@ -121,7 +84,7 @@ bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWri const IRWriter *nextIn = ∈ IRWriter *nextOut = &temp[1]; for (size_t i = 0; i < c - 1; ++i) { - if (passes[i](*nextIn, *nextOut)) { + if (passes[i](*nextIn, *nextOut, opts)) { logBlocks = true; } @@ -129,21 +92,16 @@ bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWri nextIn = &temp[0]; } - if (passes[c - 1](*nextIn, out)) { + if (passes[c - 1](*nextIn, out, opts)) { logBlocks = true; } return logBlocks; } -bool OptimizeFPMoves(const IRWriter &in, IRWriter &out) { - const u32 *constants = !in.GetConstants().empty() ? &in.GetConstants()[0] : nullptr; +bool OptimizeFPMoves(const IRWriter &in, IRWriter &out, const IROptions &opts) { bool logBlocks = false; - IRInst prev; - prev.op = IROp::Nop; - prev.dest = 0; - prev.src1 = 0; - prev.src2 = 0; + IRInst prev{ IROp::Nop }; for (int i = 0; i < (int)in.GetInstructions().size(); i++) { IRInst inst = in.GetInstructions()[i]; switch (inst.op) { @@ -172,7 +130,7 @@ bool OptimizeFPMoves(const IRWriter &in, IRWriter &out) { // AddConst a0, sp, 0x30 // LoadVec4 v16, sp, 0x30 if (prev.op == IROp::AddConst && prev.dest == inst.src1 && prev.dest != prev.src1 && prev.src1 == MIPS_REG_SP) { - inst.src2 = out.AddConstant(constants[prev.src2] + constants[inst.src2]); + inst.constant += prev.constant; inst.src1 = prev.src1; logBlocks = 1; } else { @@ -182,7 +140,7 @@ bool OptimizeFPMoves(const IRWriter &in, IRWriter &out) { break; */ default: - WriteInstWithConstants(in, out, constants, inst); + out.Write(inst); break; } prev = inst; @@ -191,7 +149,7 @@ bool OptimizeFPMoves(const IRWriter &in, IRWriter &out) { } // Might be useful later on x86. -bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out) { +bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out, const IROptions &opts) { bool logBlocks = false; for (int i = 0; i < (int)in.GetInstructions().size(); i++) { IRInst inst = in.GetInstructions()[i]; @@ -238,28 +196,184 @@ bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out) { break; } } - // Can reuse the old constants array - not touching constants in this pass. - for (u32 value : in.GetConstants()) { - out.AddConstant(value); - } return logBlocks; } -bool PropagateConstants(const IRWriter &in, IRWriter &out) { +bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions &opts) { + bool logBlocks = false; + for (int i = 0, n = (int)in.GetInstructions().size(); i < n; ++i) { + const IRInst &inst = in.GetInstructions()[i]; + + // TODO: Reorder or look ahead to combine? + + auto nextOp = [&]() -> const IRInst &{ + return in.GetInstructions()[i + 1]; + }; + + auto combineOpposite = [&](IROp matchOp, int matchOff, IROp replaceOp, int replaceOff) { + if (!opts.unalignedLoadStore || i + 1 >= n) + return false; + const IRInst &next = nextOp(); + if (next.op != matchOp || next.dest != inst.dest || next.src1 != inst.src1) + return false; + if (inst.constant + matchOff != next.constant) + return false; + + // Write out one unaligned op. + out.Write(replaceOp, inst.dest, inst.src1, out.AddConstant(inst.constant + replaceOff)); + // Skip the next one, replaced. + i++; + return true; + }; + + auto addCommonProlog = [&]() { + // IRTEMP_LR_ADDR = rs + imm + out.Write(IROp::AddConst, IRTEMP_LR_ADDR, inst.src1, out.AddConstant(inst.constant)); + // IRTEMP_LR_SHIFT = (addr & 3) * 8 + out.Write(IROp::AndConst, IRTEMP_LR_SHIFT, IRTEMP_LR_ADDR, out.AddConstant(3)); + out.Write(IROp::ShlImm, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, 3); + // IRTEMP_LR_ADDR = addr & 0xfffffffc (for stores, later) + out.Write(IROp::AndConst, IRTEMP_LR_ADDR, IRTEMP_LR_ADDR, out.AddConstant(0xFFFFFFFC)); + // IRTEMP_LR_VALUE = RAM(IRTEMP_LR_ADDR) + out.Write(IROp::Load32, IRTEMP_LR_VALUE, IRTEMP_LR_ADDR, out.AddConstant(0)); + }; + auto addCommonStore = [&](int off = 0) { + // RAM(IRTEMP_LR_ADDR) = IRTEMP_LR_VALUE + out.Write(IROp::Store32, IRTEMP_LR_VALUE, IRTEMP_LR_ADDR, out.AddConstant(off)); + }; + + switch (inst.op) { + case IROp::Load32Left: + if (!combineOpposite(IROp::Load32Right, -3, IROp::Load32, -3)) { + addCommonProlog(); + // dest &= (0x00ffffff >> shift) + // Alternatively, could shift to a wall and back (but would require two shifts each way.) + out.WriteSetConstant(IRTEMP_LR_MASK, 0x00ffffff); + out.Write(IROp::Shr, IRTEMP_LR_MASK, IRTEMP_LR_MASK, IRTEMP_LR_SHIFT); + out.Write(IROp::And, inst.dest, inst.dest, IRTEMP_LR_MASK); + // IRTEMP_LR_SHIFT = 24 - shift + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + // IRTEMP_LR_VALUE <<= (24 - shift) + out.Write(IROp::Shl, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_SHIFT); + // dest |= IRTEMP_LR_VALUE + out.Write(IROp::Or, inst.dest, inst.dest, IRTEMP_LR_VALUE); + + bool src1Dirty = inst.dest == inst.src1; + while (i + 1 < n && !src1Dirty && nextOp().op == inst.op && nextOp().src1 == inst.src1 && (nextOp().constant & 3) == (inst.constant & 3)) { + // IRTEMP_LR_VALUE = RAM(IRTEMP_LR_ADDR + offsetDelta) + out.Write(IROp::Load32, IRTEMP_LR_VALUE, IRTEMP_LR_ADDR, out.AddConstant(nextOp().constant - inst.constant)); + + // dest &= IRTEMP_LR_MASK + out.Write(IROp::And, nextOp().dest, nextOp().dest, IRTEMP_LR_MASK); + // IRTEMP_LR_VALUE <<= (24 - shift) + out.Write(IROp::Shl, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_SHIFT); + // dest |= IRTEMP_LR_VALUE + out.Write(IROp::Or, nextOp().dest, nextOp().dest, IRTEMP_LR_VALUE); + + src1Dirty = nextOp().dest == inst.src1; + ++i; + } + } + break; + + case IROp::Load32Right: + if (!combineOpposite(IROp::Load32Left, 3, IROp::Load32, 0)) { + addCommonProlog(); + // IRTEMP_LR_VALUE >>= shift + out.Write(IROp::Shr, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_SHIFT); + // IRTEMP_LR_SHIFT = 24 - shift + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + // dest &= (0xffffff00 << (24 - shift)) + // Alternatively, could shift to a wall and back (but would require two shifts each way.) + out.WriteSetConstant(IRTEMP_LR_MASK, 0xffffff00); + out.Write(IROp::Shl, IRTEMP_LR_MASK, IRTEMP_LR_MASK, IRTEMP_LR_SHIFT); + out.Write(IROp::And, inst.dest, inst.dest, IRTEMP_LR_MASK); + // dest |= IRTEMP_LR_VALUE + out.Write(IROp::Or, inst.dest, inst.dest, IRTEMP_LR_VALUE); + + // Building display lists sometimes involves a bunch of lwr in a row. + // We can generate more optimal code by combining. + bool shiftNeedsReverse = true; + bool src1Dirty = inst.dest == inst.src1; + while (i + 1 < n && !src1Dirty && nextOp().op == inst.op && nextOp().src1 == inst.src1 && (nextOp().constant & 3) == (inst.constant & 3)) { + // IRTEMP_LR_VALUE = RAM(IRTEMP_LR_ADDR + offsetDelta) + out.Write(IROp::Load32, IRTEMP_LR_VALUE, IRTEMP_LR_ADDR, out.AddConstant(nextOp().constant - inst.constant)); + + if (shiftNeedsReverse) { + // IRTEMP_LR_SHIFT = shift again + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + shiftNeedsReverse = false; + } + // IRTEMP_LR_VALUE >>= IRTEMP_LR_SHIFT + out.Write(IROp::Shr, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_SHIFT); + // dest &= IRTEMP_LR_MASK + out.Write(IROp::And, nextOp().dest, nextOp().dest, IRTEMP_LR_MASK); + // dest |= IRTEMP_LR_VALUE + out.Write(IROp::Or, nextOp().dest, nextOp().dest, IRTEMP_LR_VALUE); + + src1Dirty = nextOp().dest == inst.src1; + ++i; + } + } + break; + + case IROp::Store32Left: + if (!combineOpposite(IROp::Store32Right, -3, IROp::Store32, -3)) { + addCommonProlog(); + // IRTEMP_LR_VALUE &= 0xffffff00 << shift + out.WriteSetConstant(IRTEMP_LR_MASK, 0xffffff00); + out.Write(IROp::Shl, IRTEMP_LR_MASK, IRTEMP_LR_MASK, IRTEMP_LR_SHIFT); + out.Write(IROp::And, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_MASK); + // IRTEMP_LR_SHIFT = 24 - shift + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + // IRTEMP_LR_VALUE |= src3 >> (24 - shift) + out.Write(IROp::Shr, IRTEMP_LR_MASK, inst.src3, IRTEMP_LR_SHIFT); + out.Write(IROp::Or, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_MASK); + addCommonStore(0); + } + break; + + case IROp::Store32Right: + if (!combineOpposite(IROp::Store32Left, 3, IROp::Store32, 0)) { + addCommonProlog(); + // IRTEMP_LR_VALUE &= 0x00ffffff << (24 - shift) + out.WriteSetConstant(IRTEMP_LR_MASK, 0x00ffffff); + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + out.Write(IROp::Shr, IRTEMP_LR_MASK, IRTEMP_LR_MASK, IRTEMP_LR_SHIFT); + out.Write(IROp::And, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_MASK); + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + // IRTEMP_LR_VALUE |= src3 << shift + out.Write(IROp::Shl, IRTEMP_LR_MASK, inst.src3, IRTEMP_LR_SHIFT); + out.Write(IROp::Or, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_MASK); + addCommonStore(0); + } + break; + + default: + out.Write(inst); + break; + } + } + + return logBlocks; +} + +bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts) { IRRegCache gpr(&out); - const u32 *constants = !in.GetConstants().empty() ? &in.GetConstants()[0] : nullptr; bool logBlocks = false; for (int i = 0; i < (int)in.GetInstructions().size(); i++) { IRInst inst = in.GetInstructions()[i]; bool symmetric = true; - if (out.GetConstants().size() > 128) { - // Avoid causing a constant explosion. - goto doDefaultAndFlush; - } switch (inst.op) { case IROp::SetConst: - gpr.SetImm(inst.dest, constants[inst.src1]); + gpr.SetImm(inst.dest, inst.constant); break; case IROp::SetConstF: goto doDefault; @@ -278,6 +392,10 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out) { } if (gpr.IsImm(inst.src1) && gpr.IsImm(inst.src2)) { gpr.SetImm(inst.dest, Evaluate(gpr.GetImm(inst.src1), gpr.GetImm(inst.src2), inst.op)); + } else if (inst.op == IROp::And && gpr.IsImm(inst.src1) && gpr.GetImm(inst.src1) == 0) { + gpr.SetImm(inst.dest, 0); + } else if (inst.op == IROp::And && gpr.IsImm(inst.src2) && gpr.GetImm(inst.src2) == 0) { + gpr.SetImm(inst.dest, 0); } else if (gpr.IsImm(inst.src2)) { const u32 imm2 = gpr.GetImm(inst.src2); gpr.MapDirtyIn(inst.dest, inst.src1); @@ -327,8 +445,11 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out) { case IROp::XorConst: case IROp::SltConst: case IROp::SltUConst: - if (gpr.IsImm(inst.src1)) { - gpr.SetImm(inst.dest, Evaluate(gpr.GetImm(inst.src1), constants[inst.src2], inst.op)); + // And 0 is otherwise set to 0. Happens when optimizing lwl. + if (inst.op == IROp::AndConst && inst.constant == 0) { + gpr.SetImm(inst.dest, 0); + } else if (gpr.IsImm(inst.src1)) { + gpr.SetImm(inst.dest, Evaluate(gpr.GetImm(inst.src1), inst.constant, inst.op)); } else { gpr.MapDirtyIn(inst.dest, inst.src1); goto doDefault; @@ -426,9 +547,11 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out) { case IROp::Store8: case IROp::Store16: case IROp::Store32: + case IROp::Store32Left: + case IROp::Store32Right: if (gpr.IsImm(inst.src1) && inst.src1 != inst.dest) { gpr.MapIn(inst.dest); - out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + constants[inst.src2])); + out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); } else { gpr.MapInIn(inst.dest, inst.src1); goto doDefault; @@ -437,7 +560,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out) { case IROp::StoreFloat: case IROp::StoreVec4: if (gpr.IsImm(inst.src1)) { - out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + constants[inst.src2])); + out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); } else { gpr.MapIn(inst.src1); goto doDefault; @@ -451,7 +574,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out) { case IROp::Load32: if (gpr.IsImm(inst.src1) && inst.src1 != inst.dest) { gpr.MapDirty(inst.dest); - out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + constants[inst.src2])); + out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); } else { gpr.MapDirtyIn(inst.dest, inst.src1); goto doDefault; @@ -460,12 +583,22 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out) { case IROp::LoadFloat: case IROp::LoadVec4: if (gpr.IsImm(inst.src1)) { - out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + constants[inst.src2])); + out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); } else { gpr.MapIn(inst.src1); goto doDefault; } break; + case IROp::Load32Left: + case IROp::Load32Right: + if (gpr.IsImm(inst.src1)) { + gpr.MapIn(inst.dest); + out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); + } else { + gpr.MapInIn(inst.dest, inst.src1); + goto doDefault; + } + break; case IROp::Downcount: case IROp::SetPCConst: @@ -581,10 +714,9 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out) { case IROp::MemoryCheck: default: { - doDefaultAndFlush: gpr.FlushAll(); doDefault: - WriteInstWithConstants(in, out, constants, inst); + out.Write(inst); break; } } @@ -610,6 +742,22 @@ bool IRReadsFromGPR(const IRInst &inst, int reg) { return false; } +IRInst IRReplaceSrcGPR(const IRInst &inst, int fromReg, int toReg) { + IRInst newInst = inst; + const IRMeta *m = GetIRMeta(inst.op); + + if (m->types[1] == 'G' && inst.src1 == fromReg) { + newInst.src1 = toReg; + } + if (m->types[2] == 'G' && inst.src2 == fromReg) { + newInst.src2 = toReg; + } + if ((m->flags & (IRFLAG_SRC3 | IRFLAG_SRC3DST)) != 0 && m->types[0] == 'G' && inst.src3 == fromReg) { + newInst.src3 = toReg; + } + return newInst; +} + int IRDestGPR(const IRInst &inst) { const IRMeta *m = GetIRMeta(inst.op); @@ -619,7 +767,22 @@ int IRDestGPR(const IRInst &inst) { return -1; } -bool PurgeTemps(const IRWriter &in, IRWriter &out) { +IRInst IRReplaceDestGPR(const IRInst &inst, int fromReg, int toReg) { + IRInst newInst = inst; + const IRMeta *m = GetIRMeta(inst.op); + + if ((m->flags & IRFLAG_SRC3) == 0 && m->types[0] == 'G' && inst.dest == fromReg) { + newInst.dest = toReg; + } + return newInst; +} + +bool IRMutatesDestGPR(const IRInst &inst, int reg) { + const IRMeta *m = GetIRMeta(inst.op); + return (m->flags & IRFLAG_SRC3DST) != 0 && m->types[0] == 'G' && inst.src3 == reg; +} + +bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { std::vector insts; insts.reserve(in.GetInstructions().size()); @@ -628,14 +791,17 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out) { } int reg; + int srcReg = -1; int index; bool readByExit; }; std::vector checks; + int lastWrittenTo[256]; + memset(lastWrittenTo, -1, sizeof(lastWrittenTo)); bool logBlocks = false; for (int i = 0, n = (int)in.GetInstructions().size(); i < n; i++) { - const IRInst &inst = in.GetInstructions()[i]; + IRInst inst = in.GetInstructions()[i]; const IRMeta *m = GetIRMeta(inst.op); for (Check &check : checks) { @@ -644,8 +810,23 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out) { } if (IRReadsFromGPR(inst, check.reg)) { - // Read from, so we can't optimize out. - check.reg = 0; + // Read from, but was this just a copy? + bool mutatesReg = IRMutatesDestGPR(inst, check.reg); + bool cannotReplace = inst.op == IROp::Interpret || inst.op == IROp::CallReplacement; + if (!mutatesReg && !cannotReplace && check.srcReg >= 0 && lastWrittenTo[check.srcReg] < check.index) { + // Replace with the srcReg instead. This happens with non-nice delay slots. + inst = IRReplaceSrcGPR(inst, check.reg, check.srcReg); + } else if (!IRMutatesDestGPR(insts[check.index], check.reg) && inst.op == IROp::Mov && i == check.index + 1) { + // This happens with lwl/lwr temps. Replace the original dest. + insts[check.index] = IRReplaceDestGPR(insts[check.index], check.reg, inst.dest); + lastWrittenTo[inst.dest] = check.index; + check.reg = inst.dest; + // And swap the args for this mov, since we changed the other dest. We'll optimize this out later. + std::swap(inst.dest, inst.src1); + } else { + // Legitimately read from, so we can't optimize out. + check.reg = 0; + } } else if (check.readByExit && (m->flags & IRFLAG_EXIT) != 0) { check.reg = 0; } else if (IRDestGPR(inst) == check.reg) { @@ -662,15 +843,30 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out) { switch (dest) { case IRTEMP_0: case IRTEMP_1: + case IRTEMP_2: + case IRTEMP_3: case IRTEMP_LHS: case IRTEMP_RHS: + case IRTEMP_LR_ADDR: + case IRTEMP_LR_VALUE: + case IRTEMP_LR_MASK: + case IRTEMP_LR_SHIFT: // Unlike other ops, these don't need to persist between blocks. // So we consider them not read unless proven read. - checks.push_back(Check(dest, i, false)); + lastWrittenTo[dest] = i; + // If this is a copy, we might be able to optimize out the copy. + if (inst.op == IROp::Mov) { + Check check(dest, i, false); + check.srcReg = inst.src1; + checks.push_back(check); + } else { + checks.push_back(Check(dest, i, false)); + } break; default: - if (dest > IRTEMP_RHS) { + lastWrittenTo[dest] = i; + if (dest > IRTEMP_LR_SHIFT) { // These might sometimes be implicitly read/written by other instructions. break; } @@ -696,9 +892,6 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out) { } } - for (u32 value : in.GetConstants()) { - out.AddConstant(value); - } for (const IRInst &inst : insts) { if (inst.op != IROp::Mov || inst.dest != 0 || inst.src1 != 0) { out.Write(inst); @@ -708,11 +901,7 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out) { return logBlocks; } -bool ReduceLoads(const IRWriter &in, IRWriter &out) { - for (u32 value : in.GetConstants()) { - out.AddConstant(value); - } - +bool ReduceLoads(const IRWriter &in, IRWriter &out, const IROptions &opts) { // This tells us to skip an AND op that has been optimized out. // Maybe we could skip multiple, but that'd slow things down and is pretty uncommon. int nextSkip = -1; @@ -733,7 +922,7 @@ bool ReduceLoads(const IRWriter &in, IRWriter &out) { } if (IRReadsFromGPR(laterInst, dest)) { if (IRDestGPR(laterInst) == dest && laterInst.op == IROp::AndConst) { - const u32 mask = in.GetConstants()[laterInst.src2]; + const u32 mask = laterInst.constant; // Here we are, maybe we can reduce the load size based on the mask. if ((mask & 0xffffff00) == 0) { inst.op = IROp::Load8; @@ -765,7 +954,7 @@ bool ReduceLoads(const IRWriter &in, IRWriter &out) { return logBlocks; } -static std::vector ReorderLoadStoreOps(std::vector &ops, const u32 *consts) { +static std::vector ReorderLoadStoreOps(std::vector &ops) { if (ops.size() < 2) { return ops; } @@ -781,6 +970,8 @@ static std::vector ReorderLoadStoreOps(std::vector &ops, const u case IROp::Load16: case IROp::Load16Ext: case IROp::Load32: + case IROp::Load32Left: + case IROp::Load32Right: modifiesReg = true; if (ops[i].src1 == ops[i].dest) { // Can't ever reorder these, since it changes. @@ -791,6 +982,8 @@ static std::vector ReorderLoadStoreOps(std::vector &ops, const u case IROp::Store8: case IROp::Store16: case IROp::Store32: + case IROp::Store32Left: + case IROp::Store32Right: break; case IROp::LoadFloat: @@ -836,7 +1029,7 @@ static std::vector ReorderLoadStoreOps(std::vector &ops, const u size_t end = j; if (start + 1 < end) { std::stable_sort(ops.begin() + start, ops.begin() + end, [&](const IRInst &a, const IRInst &b) { - return consts[a.src2] < consts[b.src2]; + return a.constant < b.constant; }); } } @@ -844,7 +1037,7 @@ static std::vector ReorderLoadStoreOps(std::vector &ops, const u return ops; } -bool ReorderLoadStore(const IRWriter &in, IRWriter &out) { +bool ReorderLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) { bool logBlocks = false; enum class RegState : u8 { @@ -864,7 +1057,7 @@ bool ReorderLoadStore(const IRWriter &in, IRWriter &out) { } std::vector loadStoreUnsorted = loadStoreQueue; - std::vector loadStoreSorted = ReorderLoadStoreOps(loadStoreQueue, &in.GetConstants()[0]); + std::vector loadStoreSorted = ReorderLoadStoreOps(loadStoreQueue); if (memcmp(&loadStoreSorted[0], &loadStoreUnsorted[0], sizeof(IRInst) * loadStoreSorted.size()) != 0) { logBlocks = true; } @@ -889,6 +1082,8 @@ bool ReorderLoadStore(const IRWriter &in, IRWriter &out) { case IROp::Load16: case IROp::Load16Ext: case IROp::Load32: + case IROp::Load32Left: + case IROp::Load32Right: // To move a load up, its dest can't be changed by things we move down. if (otherRegs[inst.dest] != RegState::UNUSED || otherRegs[inst.src1] == RegState::CHANGED) { flushQueue(); @@ -901,6 +1096,8 @@ bool ReorderLoadStore(const IRWriter &in, IRWriter &out) { case IROp::Store8: case IROp::Store16: case IROp::Store32: + case IROp::Store32Left: + case IROp::Store32Right: // A store can move above even if it's read, as long as it's not changed by the other ops. if (otherRegs[inst.src3] == RegState::CHANGED || otherRegs[inst.src1] == RegState::CHANGED) { flushQueue(); @@ -1032,15 +1229,10 @@ bool ReorderLoadStore(const IRWriter &in, IRWriter &out) { break; } } - - // Can reuse the old constants array - not touching constants in this pass. - for (u32 value : in.GetConstants()) { - out.AddConstant(value); - } return logBlocks; } -bool MergeLoadStore(const IRWriter &in, IRWriter &out) { +bool MergeLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) { bool logBlocks = false; auto opsCompatible = [&](const IRInst &a, const IRInst &b, int dist) { @@ -1048,8 +1240,8 @@ bool MergeLoadStore(const IRWriter &in, IRWriter &out) { // Not similar enough at all. return false; } - u32 off1 = in.GetConstants()[a.src2]; - u32 off2 = in.GetConstants()[b.src2]; + u32 off1 = a.constant; + u32 off2 = b.constant; if (off1 + dist != off2) { // Not immediately sequential. return false; @@ -1074,16 +1266,15 @@ bool MergeLoadStore(const IRWriter &in, IRWriter &out) { break; } } - // Warning: this may generate unaligned stores. - if (c == 2 || c == 3) { + if ((c == 2 || c == 3) && opts.unalignedLoadStore) { inst.op = IROp::Store16; out.Write(inst); prev = inst; - // Skip the next one. + // Skip the next one (the 3rd will be separate.) ++i; continue; } - if (c == 4) { + if (c == 4 && opts.unalignedLoadStore) { inst.op = IROp::Store32; out.Write(inst); prev = inst; @@ -1106,8 +1297,7 @@ bool MergeLoadStore(const IRWriter &in, IRWriter &out) { break; } } - // Warning: this may generate unaligned stores. - if (c == 2) { + if (c == 2 && opts.unalignedLoadStore) { inst.op = IROp::Store32; out.Write(inst); prev = inst; @@ -1172,10 +1362,5 @@ bool MergeLoadStore(const IRWriter &in, IRWriter &out) { break; } } - - // Can reuse the old constants array - not touching constants in this pass. - for (u32 value : in.GetConstants()) { - out.AddConstant(value); - } return logBlocks; } diff --git a/Core/MIPS/IR/IRPassSimplify.h b/Core/MIPS/IR/IRPassSimplify.h index aeb2cff238..e3020a4f61 100644 --- a/Core/MIPS/IR/IRPassSimplify.h +++ b/Core/MIPS/IR/IRPassSimplify.h @@ -2,14 +2,15 @@ #include "Core/MIPS/IR/IRInst.h" -typedef bool (*IRPassFunc)(const IRWriter &in, IRWriter &out); -bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWriter &out); +typedef bool (*IRPassFunc)(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWriter &out, const IROptions &opts); // Block optimizer passes of varying usefulness. -bool PropagateConstants(const IRWriter &in, IRWriter &out); -bool PurgeTemps(const IRWriter &in, IRWriter &out); -bool ReduceLoads(const IRWriter &in, IRWriter &out); -bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out); -bool OptimizeFPMoves(const IRWriter &in, IRWriter &out); -bool ReorderLoadStore(const IRWriter &in, IRWriter &out); -bool MergeLoadStore(const IRWriter &in, IRWriter &out); +bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool ReduceLoads(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool OptimizeFPMoves(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool ReorderLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool MergeLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts); diff --git a/Core/MIPS/JitCommon/JitBlockCache.cpp b/Core/MIPS/JitCommon/JitBlockCache.cpp index ad6466e8d1..6958fc894a 100644 --- a/Core/MIPS/JitCommon/JitBlockCache.cpp +++ b/Core/MIPS/JitCommon/JitBlockCache.cpp @@ -105,7 +105,7 @@ void JitBlockCache::Clear() { block_map_.clear(); proxyBlockMap_.clear(); for (int i = 0; i < num_blocks_; i++) - DestroyBlock(i, false); + DestroyBlock(i, DestroyType::CLEAR); links_to_.clear(); num_blocks_ = 0; @@ -123,6 +123,10 @@ JitBlock *JitBlockCache::GetBlock(int no) { return &blocks_[no]; } +const JitBlock *JitBlockCache::GetBlock(int no) const { + return &blocks_[no]; +} + int JitBlockCache::AllocateBlock(u32 startAddress) { JitBlock &b = blocks_[num_blocks_]; @@ -326,7 +330,7 @@ MIPSOpcode JitBlockCache::GetEmuHackOpForBlock(int blockNum) const { return MIPSOpcode(MIPS_EMUHACK_OPCODE | off); } -int JitBlockCache::GetBlockNumberFromStartAddress(u32 addr, bool realBlocksOnly) { +int JitBlockCache::GetBlockNumberFromStartAddress(u32 addr, bool realBlocksOnly) const { if (!blocks_ || !Memory::IsValidAddress(addr)) return -1; @@ -474,7 +478,7 @@ void JitBlockCache::RestoreSavedEmuHackOps(std::vector saved) { } } -void JitBlockCache::DestroyBlock(int block_num, bool invalidate) { +void JitBlockCache::DestroyBlock(int block_num, DestroyType type) { if (block_num < 0 || block_num >= num_blocks_) { ERROR_LOG_REPORT(JIT, "DestroyBlock: Invalid block number %d", block_num); return; @@ -493,7 +497,7 @@ void JitBlockCache::DestroyBlock(int block_num, bool invalidate) { int proxied_blocknum = GetBlockNumberFromStartAddress((*b->proxyFor)[i], false); // If it was already cleared, we don't know which to destroy. if (proxied_blocknum != -1) { - DestroyBlock(proxied_blocknum, invalidate); + DestroyBlock(proxied_blocknum, type); } } b->proxyFor->clear(); @@ -513,7 +517,7 @@ void JitBlockCache::DestroyBlock(int block_num, bool invalidate) { // In this case we probably "leak" the proxy block currently (no memory leak but it'll stay enabled). if (b->invalid) { - if (invalidate) + if (type == DestroyType::INVALIDATE) ERROR_LOG(JIT, "Invalidating invalid block %d", block_num); return; } @@ -535,7 +539,10 @@ void JitBlockCache::DestroyBlock(int block_num, bool invalidate) { } if (b->checkedEntry) { - MIPSComp::jit->UnlinkBlock(b->checkedEntry, b->originalAddress); + // We can skip this if we're clearing anyway, which cuts down on protect back and forth on WX exclusive. + if (type != DestroyType::CLEAR) { + MIPSComp::jit->UnlinkBlock(b->checkedEntry, b->originalAddress); + } } else { ERROR_LOG(JIT, "Unlinking block with no entry: %08x (%d)", b->originalAddress, block_num); } @@ -567,7 +574,7 @@ void JitBlockCache::InvalidateICache(u32 address, const u32 length) { const u32 blockStart = next->first.second; const u32 blockEnd = next->first.first; if (blockStart < pEnd && blockEnd > pAddr) { - DestroyBlock(next->second, true); + DestroyBlock(next->second, DestroyType::INVALIDATE); // Our iterator is now invalid. Break and search again. // Most of the time there shouldn't be a bunch of matching blocks. goto restart; @@ -587,7 +594,7 @@ void JitBlockCache::InvalidateChangedBlocks() { const u32 emuhack = GetEmuHackOpForBlock(block_num).encoding; if (Memory::ReadUnchecked_U32(b.originalAddress) != emuhack) { DEBUG_LOG(JIT, "Invalidating changed block at %08x", b.originalAddress); - DestroyBlock(block_num, true); + DestroyBlock(block_num, DestroyType::INVALIDATE); } } } @@ -607,12 +614,12 @@ int JitBlockCache::GetBlockExitSize() { #endif } -void JitBlockCache::ComputeStats(BlockCacheStats &bcStats) { +void JitBlockCache::ComputeStats(BlockCacheStats &bcStats) const { double totalBloat = 0.0; double maxBloat = 0.0; double minBloat = 1000000000.0; for (int i = 0; i < num_blocks_; i++) { - JitBlock *b = GetBlock(i); + const JitBlock *b = GetBlock(i); double codeSize = (double)b->codeSize; if (codeSize == 0) continue; @@ -634,3 +641,25 @@ void JitBlockCache::ComputeStats(BlockCacheStats &bcStats) { bcStats.maxBloat = maxBloat; bcStats.avgBloat = totalBloat / (double)num_blocks_; } + +JitBlockDebugInfo JitBlockCache::GetBlockDebugInfo(int blockNum) const { + JitBlockDebugInfo debugInfo{}; + const JitBlock *block = GetBlock(blockNum); + debugInfo.originalAddress = block->originalAddress; + for (u32 addr = block->originalAddress; addr <= block->originalAddress + block->originalSize * 4; addr += 4) { + char temp[256]; + MIPSDisAsm(Memory::Read_Instruction(addr), addr, temp, true); + std::string mipsDis = temp; + debugInfo.origDisasm.push_back(mipsDis); + } + +#if defined(ARM) + debugInfo.targetDisasm = DisassembleArm2(block->normalEntry, block->codeSize); +#elif defined(ARM64) + debugInfo.targetDisasm = DisassembleArm64(block->normalEntry, block->codeSize); +#elif defined(_M_IX86) || defined(_M_X64) + debugInfo.targetDisasm = DisassembleX86(block->normalEntry, block->codeSize); +#endif + + return debugInfo; +} diff --git a/Core/MIPS/JitCommon/JitBlockCache.h b/Core/MIPS/JitCommon/JitBlockCache.h index 7041f31284..ce609b8ada 100644 --- a/Core/MIPS/JitCommon/JitBlockCache.h +++ b/Core/MIPS/JitCommon/JitBlockCache.h @@ -43,6 +43,13 @@ struct BlockCacheStats { std::map bloatMap; }; +enum class DestroyType { + DESTROY, + INVALIDATE, + // Skips jit unlink, since it'll be poisoned anyway. + CLEAR, +}; + // Define this in order to get VTune profile support for the Jit generated code. // Add the VTune include/lib directories to the project directories to get this to build. // #define USE_VTUNE @@ -86,7 +93,24 @@ struct JitBlock { typedef void (*CompiledCode)(); -class JitBlockCache { +struct JitBlockDebugInfo { + uint32_t originalAddress; + std::vector origDisasm; + std::vector irDisasm; // if any + std::vector targetDisasm; +}; + +class JitBlockCacheDebugInterface { +public: + virtual int GetNumBlocks() const = 0; + virtual int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true) const = 0; + virtual JitBlockDebugInfo GetBlockDebugInfo(int blockNum) const = 0; + virtual void ComputeStats(BlockCacheStats &bcStats) const = 0; + + virtual ~JitBlockCacheDebugInterface() {} +}; + +class JitBlockCache : public JitBlockCacheDebugInterface { public: JitBlockCache(MIPSState *mips_, CodeBlockCommon *codeBlock); ~JitBlockCache(); @@ -102,13 +126,14 @@ public: void Reset(); bool IsFull() const; - void ComputeStats(BlockCacheStats &bcStats); + void ComputeStats(BlockCacheStats &bcStats) const override; // Code Cache JitBlock *GetBlock(int block_num); + const JitBlock *GetBlock(int block_num) const; // Fast way to get a block. Only works on the first source-cpu instruction of a block. - int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true); + int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true) const override; // slower, but can get numbers from within blocks, not just the first instruction. // WARNING! WILL NOT WORK WITH JIT INLINING ENABLED (not yet a feature but will be soon) @@ -126,17 +151,19 @@ public: // DOES NOT WORK CORRECTLY WITH JIT INLINING void InvalidateICache(u32 address, const u32 length); void InvalidateChangedBlocks(); - void DestroyBlock(int block_num, bool invalidate); + void DestroyBlock(int block_num, DestroyType type); // No jit operations may be run between these calls. // Meant to be used to make memory safe for savestates, memcpy, etc. std::vector SaveAndClearEmuHackOps(); void RestoreSavedEmuHackOps(std::vector saved); - int GetNumBlocks() const { return num_blocks_; } + int GetNumBlocks() const override { return num_blocks_; } static int GetBlockExitSize(); + JitBlockDebugInfo GetBlockDebugInfo(int blockNum) const override; + enum { MAX_BLOCK_INSTRUCTIONS = 0x4000, }; diff --git a/Core/MIPS/JitCommon/JitCommon.h b/Core/MIPS/JitCommon/JitCommon.h index 6296506a01..33e07b1955 100644 --- a/Core/MIPS/JitCommon/JitCommon.h +++ b/Core/MIPS/JitCommon/JitCommon.h @@ -31,6 +31,7 @@ std::vector DisassembleX86(const u8 *data, int size); struct JitBlock; class JitBlockCache; +class JitBlockCacheDebugInterface; class PointerWrap; #ifdef USING_QT_UI @@ -123,10 +124,12 @@ namespace MIPSComp { virtual bool DescribeCodePtr(const u8 *ptr, std::string &name) = 0; virtual const u8 *GetDispatcher() const = 0; virtual JitBlockCache *GetBlockCache() = 0; + virtual JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() = 0; virtual void InvalidateCacheAt(u32 em_address, int length = 4) = 0; virtual void DoState(PointerWrap &p) = 0; virtual void RunLoopUntil(u64 globalticks) = 0; virtual void Compile(u32 em_address) = 0; + virtual void CompileFunction(u32 start_address, u32 length) { } virtual void ClearCache() = 0; virtual MIPSOpcode GetOriginalOp(MIPSOpcode op) = 0; diff --git a/Core/MIPS/JitCommon/JitState.cpp b/Core/MIPS/JitCommon/JitState.cpp index 1d147d4c17..7a603daf50 100644 --- a/Core/MIPS/JitCommon/JitState.cpp +++ b/Core/MIPS/JitCommon/JitState.cpp @@ -51,8 +51,11 @@ namespace MIPSComp { continueMaxInstructions = 300; useStaticAlloc = false; + enablePointerify = false; #if PPSSPP_ARCH(ARM64) useStaticAlloc = true; + // iOS/etc. may disable at runtime if Memory::base is not nicely aligned. + enablePointerify = true; #endif } } diff --git a/Core/MIPS/JitCommon/JitState.h b/Core/MIPS/JitCommon/JitState.h index b1fe63892c..6d2c8d0937 100644 --- a/Core/MIPS/JitCommon/JitState.h +++ b/Core/MIPS/JitCommon/JitState.h @@ -56,15 +56,6 @@ namespace MIPSComp { AFTER_MEMCHECK_CLEANUP = 0x04, }; - JitState() - : hasSetRounding(0), - lastSetRounding(0), - currentRoundingFunc(nullptr), - startDefaultPrefix(true), - prefixSFlag(PREFIX_UNKNOWN), - prefixTFlag(PREFIX_UNKNOWN), - prefixDFlag(PREFIX_UNKNOWN) {} - u32 compilerPC; u32 blockStart; u32 lastContinuedPC; @@ -78,20 +69,21 @@ namespace MIPSComp { int numInstructions; bool compiling; // TODO: get rid of this in favor of using analysis results to determine end of block bool hadBreakpoints; + bool preloading = false; JitBlock *curBlock; - u8 hasSetRounding; - u8 lastSetRounding; - const u8 *currentRoundingFunc; + u8 hasSetRounding = 0; + u8 lastSetRounding = 0; + const u8 *currentRoundingFunc = nullptr; // VFPU prefix magic - bool startDefaultPrefix; + bool startDefaultPrefix = true; u32 prefixS; u32 prefixT; u32 prefixD; - PrefixState prefixSFlag; - PrefixState prefixTFlag; - PrefixState prefixDFlag; + PrefixState prefixSFlag = PREFIX_UNKNOWN; + PrefixState prefixTFlag = PREFIX_UNKNOWN; + PrefixState prefixDFlag = PREFIX_UNKNOWN; void PrefixStart() { if (startDefaultPrefix) { @@ -198,6 +190,7 @@ namespace MIPSComp { // ARM64 only bool useASIMDVFPU; bool useStaticAlloc; + bool enablePointerify; // Common bool enableBlocklink; diff --git a/Core/MIPS/MIPS/MipsJit.h b/Core/MIPS/MIPS/MipsJit.h index c0ff28582d..bc6a526f01 100644 --- a/Core/MIPS/MIPS/MipsJit.h +++ b/Core/MIPS/MIPS/MipsJit.h @@ -129,6 +129,7 @@ public: void Comp_Vbfy(MIPSOpcode op) {} JitBlockCache *GetBlockCache() override { return &blocks; } + JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks; } std::vector SaveAndClearEmuHackOps() override { return blocks.SaveAndClearEmuHackOps(); } void RestoreSavedEmuHackOps(std::vector saved) override { blocks.RestoreSavedEmuHackOps(saved); } diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index d651ec7827..98d07490ab 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -21,6 +21,7 @@ #include #include +#include "base/timeutil.h" #include "ext/cityhash/city.h" #include "Common/FileUtil.h" #include "Core/Config.h" @@ -382,6 +383,7 @@ static const HardHashTableEntry hardcodedHashes[] = { { 0xb0ef265e87899f0a, 32, "vector_divide_t_s", }, { 0xb183a37baa12607b, 32, "vscl_t", }, { 0xb1a3e60a89af9857, 20, "fabs", }, + { 0xb25670ff47b4843d, 232, "starocean_clear_framebuf" }, { 0xb3fef47fb27d57c9, 44, "vector_scale_t", }, { 0xb43fd5078ae78029, 84, "send_commandi_stall", }, { 0xb43ffbd4dc446dd2, 324, "atan2f", }, @@ -903,6 +905,32 @@ skip: } } + void PrecompileFunction(u32 startAddr, u32 length) { + // Direct calls to this ignore the bPreloadFunctions flag, since it's just for stubs. + if (MIPSComp::jit) { + MIPSComp::jit->CompileFunction(startAddr, length); + } + } + + void PrecompileFunctions() { + if (!g_Config.bPreloadFunctions) { + return; + } + std::lock_guard guard(functions_lock); + + // TODO: Load from cache file if available instead. + + double st = real_time_now(); + for (auto iter = functions.begin(), end = functions.end(); iter != end; iter++) { + const AnalyzedFunction &f = *iter; + + PrecompileFunction(f.start, f.end - f.start + 4); + } + double et = real_time_now(); + + NOTICE_LOG(JIT, "Precompiled %d MIPS functions in %0.2f milliseconds", (int)functions.size(), (et - st) * 1000.0); + } + static const char *DefaultFunctionName(char buffer[256], u32 startAddr) { sprintf(buffer, "z_un_%08x", startAddr); return buffer; @@ -1123,8 +1151,10 @@ skip: } } - currentFunction.end = addr + 4; - functions.push_back(currentFunction); + if (addr <= endAddr) { + currentFunction.end = addr + 4; + functions.push_back(currentFunction); + } for (auto iter = functions.begin(); iter != functions.end(); iter++) { iter->size = iter->end - iter->start + 4; diff --git a/Core/MIPS/MIPSAnalyst.h b/Core/MIPS/MIPSAnalyst.h index 867d96895c..9d6683c48a 100644 --- a/Core/MIPS/MIPSAnalyst.h +++ b/Core/MIPS/MIPSAnalyst.h @@ -107,7 +107,8 @@ namespace MIPSAnalyst void RegisterFunction(u32 startAddr, u32 size, const char *name); void ScanForFunctions(u32 startAddr, u32 endAddr, bool insertSymbols); void ForgetFunctions(u32 startAddr, u32 endAddr); - void CompileLeafs(); + void PrecompileFunctions(); + void PrecompileFunction(u32 startAddr, u32 length); void SetHashMapFilename(const std::string& filename = ""); void LoadBuiltinHashMap(); diff --git a/Core/MIPS/x86/Jit.cpp b/Core/MIPS/x86/Jit.cpp index e220336b2a..48e098cc3c 100644 --- a/Core/MIPS/x86/Jit.cpp +++ b/Core/MIPS/x86/Jit.cpp @@ -286,7 +286,7 @@ void Jit::Compile(u32 em_address) { // Drat. The VFPU hit an uneaten prefix at the end of a block. if (js.startDefaultPrefix && js.MayHavePrefix()) { - WARN_LOG(JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4); + WARN_LOG_REPORT(JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4); js.LogPrefix(); // Let's try that one more time. We won't get back here because we toggled the value. diff --git a/Core/MIPS/x86/Jit.h b/Core/MIPS/x86/Jit.h index 2a35212140..d6ff79b003 100644 --- a/Core/MIPS/x86/Jit.h +++ b/Core/MIPS/x86/Jit.h @@ -158,6 +158,8 @@ public: void UpdateRoundingMode(); JitBlockCache *GetBlockCache() override { return &blocks; } + JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks; } + MIPSOpcode GetOriginalOp(MIPSOpcode op) override; std::vector SaveAndClearEmuHackOps() override { return blocks.SaveAndClearEmuHackOps(); } diff --git a/Core/MIPS/x86/JitSafeMem.cpp b/Core/MIPS/x86/JitSafeMem.cpp index 339f5e0ec1..56374ed828 100644 --- a/Core/MIPS/x86/JitSafeMem.cpp +++ b/Core/MIPS/x86/JitSafeMem.cpp @@ -440,6 +440,7 @@ void JitSafeMemFuncs::Init(ThunkManager *thunks) { AllocCodeSpace(FUNCS_ARENA_SIZE); thunks_ = thunks; + BeginWrite(); readU32 = GetCodePtr(); CreateReadFunc(32, (const void *)&Memory::Read_U32); readU16 = GetCodePtr(); @@ -453,6 +454,7 @@ void JitSafeMemFuncs::Init(ThunkManager *thunks) { CreateWriteFunc(16, (const void *)&Memory::Write_U16); writeU8 = GetCodePtr(); CreateWriteFunc(8, (const void *)&Memory::Write_U8); + EndWrite(); } void JitSafeMemFuncs::Shutdown() { diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index 9df9f4e5ef..1a910edb05 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -15,6 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include #include "file/file_util.h" #include "util/text/utf8.h" @@ -250,7 +251,20 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) { g_Config.loadGameConfig(id); host->SendUIMessage("config_loaded", ""); INFO_LOG(LOADER,"Loading %s...", bootpath.c_str()); - return __KernelLoadExec(bootpath.c_str(), 0, error_string); + + std::thread th([bootpath] { + // TODO: We can't use the initial error_string pointer. + bool success = __KernelLoadExec(bootpath.c_str(), 0, &PSP_CoreParameter().errorString); + if (success && coreState == CORE_POWERUP) { + coreState = PSP_CoreParameter().startPaused ? CORE_STEPPING : CORE_RUNNING; + } else { + coreState = CORE_ERROR; + // TODO: This is a crummy way to communicate the error... + PSP_CoreParameter().fileToStart = ""; + } + }); + th.detach(); + return true; } static std::string NormalizePath(const std::string &path) { @@ -354,13 +368,34 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { } // End of temporary code - return __KernelLoadExec(finalName.c_str(), 0, error_string); + std::thread th([finalName] { + bool success = __KernelLoadExec(finalName.c_str(), 0, &PSP_CoreParameter().errorString); + if (success && coreState == CORE_POWERUP) { + coreState = PSP_CoreParameter().startPaused ? CORE_STEPPING : CORE_RUNNING; + } else { + coreState = CORE_ERROR; + // TODO: This is a crummy way to communicate the error... + PSP_CoreParameter().fileToStart = ""; + } + }); + th.detach(); + return true; } bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) { BlobFileSystem *umd = new BlobFileSystem(&pspFileSystem, fileLoader, "data.ppdmp"); pspFileSystem.Mount("disc0:", umd); - __KernelLoadGEDump("disc0:/data.ppdmp", error_string); + std::thread th([] { + bool success = __KernelLoadGEDump("disc0:/data.ppdmp", &PSP_CoreParameter().errorString); + if (success && coreState == CORE_POWERUP) { + coreState = PSP_CoreParameter().startPaused ? CORE_STEPPING : CORE_RUNNING; + } else { + coreState = CORE_ERROR; + // TODO: This is a crummy way to communicate the error... + PSP_CoreParameter().fileToStart = ""; + } + }); + th.detach(); return true; } diff --git a/Core/System.cpp b/Core/System.cpp index 0c274f58f2..cc41c39313 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -29,6 +29,7 @@ #include #include +#include "base/timeutil.h" #include "math/math_util.h" #include "thread/threadutil.h" #include "util/text/utf8.h" @@ -134,6 +135,13 @@ void Audio_Init() { } } +void Audio_Shutdown() { + if (audioInitialized) { + audioInitialized = false; + host->ShutdownSound(); + } +} + bool IsOnSeparateCPUThread() { if (cpuThread != nullptr) { return cpuThreadID == std::this_thread::get_id(); @@ -174,6 +182,8 @@ bool CPU_NextStateNot(CPUThreadState from, CPUThreadState to) { } bool CPU_IsReady() { + if (coreState == CORE_POWERUP) + return false; return cpuThreadState == CPU_THREAD_RUNNING || cpuThreadState == CPU_THREAD_NOT_RUNNING; } @@ -263,7 +273,8 @@ void CPU_Init() { // TODO: Check Game INI here for settings, patches and cheats, and modify coreParameter accordingly - // Why did we check for CORE_POWERDOWN here? + // If they shut down early, we'll catch it when load completes. + // Note: this may return before init is complete, which is checked if CPU_IsReady(). if (!LoadFile(&loadedFile, &coreParameter.errorString)) { CPU_Shutdown(); coreParameter.fileToStart = ""; @@ -275,8 +286,6 @@ void CPU_Init() { if (coreParameter.updateRecent) { g_Config.AddRecent(filename); } - - coreState = coreParameter.startPaused ? CORE_STEPPING : CORE_RUNNING; } void CPU_Shutdown() { @@ -290,8 +299,7 @@ void CPU_Shutdown() { __KernelShutdown(); HLEShutdown(); if (coreParameter.enableSound) { - host->ShutdownSound(); - audioInitialized = false; // deleted in ShutdownSound + Audio_Shutdown(); } pspFileSystem.Shutdown(); mipsr4k.Shutdown(); @@ -458,7 +466,8 @@ bool PSP_Init(const CoreParameter &coreParam, std::string *error_string) { CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsReady); } - PSP_InitUpdate(error_string); + while (!PSP_InitUpdate(error_string)) + sleep_ms(10); return pspIsInited; } diff --git a/Core/System.h b/Core/System.h index 26d1d6e19b..9cc4cfdc89 100644 --- a/Core/System.h +++ b/Core/System.h @@ -77,6 +77,7 @@ void PSP_RunLoopFor(int cycles); void Core_UpdateDebugStats(bool collectStats); void Audio_Init(); +void Audio_Shutdown(); bool IsOnSeparateCPUThread(); bool IsAudioInitialised(); diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index aa8d9f007e..20093b8c64 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -211,7 +211,7 @@ void FramebufferManagerCommon::SetNumExtraFBOs(int num) { } currentRenderVfb_ = 0; if (num != 0) - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } // Heuristics to figure out the size of FBO to create. @@ -609,12 +609,12 @@ void FramebufferManagerCommon::NotifyRenderFramebufferSwitched(VirtualFramebuffe // FBO in a frame. This means that some games won't be able to avoid the on-some-GPUs // performance-crushing framebuffer reloads from RAM, but we'll have to live with that. if (vfb->last_frame_render != gpuStats.numFlips) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } else { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } } else { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } } else { // This should only happen very briefly when toggling useBufferedRendering_. @@ -723,7 +723,7 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int MakePixelTexture(srcPixels, srcPixelFormat, srcStride, width, height, u1, v1); if (useBufferedRendering_ && vfb && vfb->fbo) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); SetViewport2D(0, 0, vfb->renderWidth, vfb->renderHeight); draw_->SetScissorRect(0, 0, vfb->renderWidth, vfb->renderHeight); } else { @@ -740,6 +740,7 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int DrawTextureFlags flags = (vfb || g_Config.iBufFilter == SCALE_LINEAR) ? DRAWTEX_LINEAR : DRAWTEX_NEAREST; Bind2DShader(); DrawActiveTexture(dstX, dstY, width, height, vfb->bufferWidth, vfb->bufferHeight, u0, v0, u1, v1, ROTATION_LOCKED_HORIZONTAL, flags); + gpuStats.numUploads++; } void FramebufferManagerCommon::CopyFramebufferForColorTexture(VirtualFramebuffer *dst, VirtualFramebuffer *src, int flags) { @@ -850,7 +851,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { DEBUG_LOG(FRAMEBUF, "Display disabled, displaying only black"); // No framebuffer to display! Clear to black. if (useBufferedRendering_) { - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } return; } @@ -915,7 +916,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { if (!vfb) { if (useBufferedRendering_) { // Bind and clear the backbuffer. This should be the first time during the frame that it's bound. - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } // Just a pointer to plain memory to draw. We should create a framebuffer, then draw to it. SetViewport2D(0, 0, pixelWidth_, pixelHeight_); @@ -928,7 +929,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { // No framebuffer to display! Clear to black. if (useBufferedRendering_) { // Bind and clear the backbuffer. This should be the first time during the frame that it's bound. - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } return; } @@ -965,7 +966,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { float v1 = (272.0f + offsetY) / (float)vfb->bufferHeight; if (!usePostShader_) { - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); draw_->BindFramebufferAsTexture(vfb->fbo, 0, Draw::FB_COLOR_BIT, 0); draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_); DrawTextureFlags flags = g_Config.iBufFilter == SCALE_LINEAR ? DRAWTEX_LINEAR : DRAWTEX_NEAREST; @@ -989,7 +990,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { } } else if (usePostShader_ && extraFBOs_.size() == 1 && !postShaderAtOutputResolution_) { // An additional pass, post-processing shader to the extra FBO. - draw_->BindFramebufferAsRenderTarget(extraFBOs_[0], { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(extraFBOs_[0], { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); draw_->BindFramebufferAsTexture(vfb->fbo, 0, Draw::FB_COLOR_BIT, 0); int fbo_w, fbo_h; draw_->GetFramebufferDimensions(extraFBOs_[0], &fbo_w, &fbo_h); @@ -1002,7 +1003,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { DrawTextureFlags flags = g_Config.iBufFilter == SCALE_LINEAR ? DRAWTEX_LINEAR : DRAWTEX_NEAREST; DrawActiveTexture(0, 0, fbo_w, fbo_h, fbo_w, fbo_h, 0.0f, 0.0f, 1.0f, 1.0f, ROTATION_LOCKED_HORIZONTAL, flags); - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); SetViewport2D(0, 0, pixelWidth_, pixelHeight_); // Use the extra FBO, with applied post-processing shader, as a texture. @@ -1035,12 +1036,12 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { /* if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) { - draw_->BindFramebufferAsRenderTarget(extraFBOs_[0], { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(extraFBOs_[0], { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); GLenum attachments[3] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT }; glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, attachments); }*/ } else { - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); draw_->BindFramebufferAsTexture(vfb->fbo, 0, Draw::FB_COLOR_BIT, 0); draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_); // We are doing the DrawActiveTexture call directly to the backbuffer here. Hence, we must @@ -1191,17 +1192,17 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, if (old.fbo) { INFO_LOG(FRAMEBUF, "Resizing FBO for %08x : %d x %d x %d", vfb->fb_address, w, h, vfb->format); if (vfb->fbo) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); if (!skipCopy && !g_Config.bDisableSlowFramebufEffects) { BlitFramebuffer(vfb, 0, 0, &old, 0, 0, std::min((u16)oldWidth, std::min(vfb->bufferWidth, vfb->width)), std::min((u16)oldHeight, std::min(vfb->height, vfb->bufferHeight)), 0); } } fbosToDelete_.push_back(old.fbo); if (needGLESRebinds_) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } } else { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } if (!vfb->fbo) { @@ -2116,10 +2117,10 @@ void FramebufferManagerCommon::DownloadFramebufferForClut(u32 fb_address, u32 lo void FramebufferManagerCommon::RebindFramebuffer() { if (currentRenderVfb_ && currentRenderVfb_->fbo) { - draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } else { // Should this even happen? It could while debugging, but maybe we can just skip binding at all. - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE); } diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index 80a9b83d7b..fa69f91f08 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -785,12 +785,10 @@ static const BlendEq eqLookup[] = { static BlendFactor toDualSource(BlendFactor blendfunc) { switch (blendfunc) { -#if !defined(USING_GLES2) // TODO: Remove when we have better headers case BlendFactor::SRC_ALPHA: return BlendFactor::SRC1_ALPHA; case BlendFactor::ONE_MINUS_SRC_ALPHA: return BlendFactor::ONE_MINUS_SRC1_ALPHA; -#endif default: return blendfunc; } diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index b55b9c97b4..2d607abf62 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include +#include "profiler/profiler.h" #include "Common/ColorConv.h" #include "Common/MemoryUtil.h" #include "Common/StringUtils.h" @@ -1473,6 +1474,7 @@ void TextureCacheCommon::ApplyTexture() { } if (nextNeedsRehash_) { + PROFILE_THIS_SCOPE("texhash"); // Update the hash on the texture. int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); @@ -1548,7 +1550,11 @@ void TextureCacheCommon::DeleteTexture(TexCache::iterator it) { bool TextureCacheCommon::CheckFullHash(TexCacheEntry *entry, bool &doDelete) { int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); - u32 fullhash = QuickTexHash(replacer_, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); + u32 fullhash; + { + PROFILE_THIS_SCOPE("texhash"); + fullhash = QuickTexHash(replacer_, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); + } if (fullhash == entry->fullhash) { if (g_Config.bTextureBackoffCache) { diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index 4cf4d37287..dccd4dccf9 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -502,28 +502,28 @@ void VertexDecoder::Step_Color5551() const { u8 *c = decoded_ + decFmt.c0off; u16 cdata = *(u16_le *)(ptr_ + coloff); + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (cdata >> 15) != 0; c[0] = Convert5To8(cdata & 0x1f); c[1] = Convert5To8((cdata >> 5) & 0x1f); c[2] = Convert5To8((cdata >> 10) & 0x1f); c[3] = (cdata >> 15) ? 255 : 0; - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && c[3] != 0; } void VertexDecoder::Step_Color4444() const { u8 *c = decoded_ + decFmt.c0off; u16 cdata = *(u16_le *)(ptr_ + coloff); + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (cdata >> 12) == 0xF; for (int j = 0; j < 4; j++) c[j] = Convert4To8((cdata >> (j * 4)) & 0xF); - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && c[3] == 255; } void VertexDecoder::Step_Color8888() const { u8 *c = decoded_ + decFmt.c0off; const u8 *cdata = (const u8*)(ptr_ + coloff); + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && cdata[3] == 255; memcpy(c, cdata, sizeof(u8) * 4); - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && c[3] == 255; } void VertexDecoder::Step_Color565Morph() const @@ -559,7 +559,7 @@ void VertexDecoder::Step_Color5551Morph() const for (int i = 0; i < 4; i++) { c[i] = clamp_u8((int)col[i]); } - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && c[3] == 255; + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (int)col[3] >= 255; } void VertexDecoder::Step_Color4444Morph() const @@ -575,7 +575,7 @@ void VertexDecoder::Step_Color4444Morph() const for (int i = 0; i < 4; i++) { c[i] = clamp_u8((int)col[i]); } - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && c[3] == 255; + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (int)col[3] >= 255; } void VertexDecoder::Step_Color8888Morph() const @@ -591,7 +591,7 @@ void VertexDecoder::Step_Color8888Morph() const for (int i = 0; i < 4; i++) { c[i] = clamp_u8((int)col[i]); } - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && c[3] == 255; + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (int)col[3] >= 255; } void VertexDecoder::Step_NormalS8() const diff --git a/GPU/D3D11/FramebufferManagerD3D11.cpp b/GPU/D3D11/FramebufferManagerD3D11.cpp index 4d29240d2a..6da4b477c1 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.cpp +++ b/GPU/D3D11/FramebufferManagerD3D11.cpp @@ -461,10 +461,10 @@ void FramebufferManagerD3D11::BindPostShader(const PostShaderUniforms &uniforms) void FramebufferManagerD3D11::RebindFramebuffer() { if (currentRenderVfb_ && currentRenderVfb_->fbo) { - draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } else { // Should this even happen? - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } } @@ -482,7 +482,7 @@ void FramebufferManagerD3D11::ReformatFramebufferFrom(VirtualFramebuffer *vfb, G // and blit with a shader to that, then replace the FBO on vfb. Stencil would still be complex // to exactly reproduce in 4444 and 8888 formats. if (old == GE_FORMAT_565) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); // TODO: There's no way this does anything useful :( context_->OMSetDepthStencilState(stockD3D11.depthDisabledStencilWrite, 0xFF); @@ -591,7 +591,7 @@ bool FramebufferManagerD3D11::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) return false; } - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); return true; } @@ -634,7 +634,7 @@ void FramebufferManagerD3D11::SimpleBlit( // Unbind the texture first to avoid the D3D11 hazard check (can't set render target to things bound as textures and vice versa, not even temporarily). draw_->BindTexture(0, nullptr); - draw_->BindFramebufferAsRenderTarget(dest, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(dest, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); draw_->BindFramebufferAsTexture(src, 0, Draw::FB_COLOR_BIT, 0); Bind2DShader(); @@ -657,7 +657,7 @@ void FramebufferManagerD3D11::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, if (!dst->fbo || !src->fbo || !useBufferedRendering_) { // This can happen if they recently switched from non-buffered. if (useBufferedRendering_) { - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } return; } diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index df3a9aa6f1..24dcfdd734 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -527,7 +527,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" + "Readbacks: %d, uploads: %d\n" "Vertex, Fragment shaders loaded: %i, %i\n", gpuStats.msProcessingDisplayLists * 1000.0f, gpuStats.numDrawCalls, @@ -546,6 +546,7 @@ void GPU_D3D11::GetStats(char *buffer, size_t bufsize) { gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, gpuStats.numReadbacks, + gpuStats.numUploads, shaderManagerD3D11_->GetNumVertexShaders(), shaderManagerD3D11_->GetNumFragmentShaders() ); diff --git a/GPU/D3D11/StencilBufferD3D11.cpp b/GPU/D3D11/StencilBufferD3D11.cpp index 9a8a44a888..f489f5838a 100644 --- a/GPU/D3D11/StencilBufferD3D11.cpp +++ b/GPU/D3D11/StencilBufferD3D11.cpp @@ -161,7 +161,7 @@ bool FramebufferManagerD3D11::NotifyStencilUpload(u32 addr, int size, bool skipZ float v1 = 1.0f; MakePixelTexture(src, dstBuffer->format, dstBuffer->fb_stride, dstBuffer->bufferWidth, dstBuffer->bufferHeight, u1, v1); if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); } else { // something is wrong... } diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index 4132b2d8e6..f63a7028c7 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -398,7 +398,7 @@ void TextureCacheD3D11::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFra context_->PSSetShaderResources(1, 1, &clutTexture); framebufferManagerD3D11_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_SKIP_COPY); context_->PSSetSamplers(0, 1, &stockD3D11.samplerPoint2DWrap); - draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); shaderApply.Shade(); framebufferManagerD3D11_->RebindFramebuffer(); diff --git a/GPU/Directx9/FramebufferDX9.cpp b/GPU/Directx9/FramebufferDX9.cpp index b80e0d41c0..fa0235a2c5 100644 --- a/GPU/Directx9/FramebufferDX9.cpp +++ b/GPU/Directx9/FramebufferDX9.cpp @@ -319,7 +319,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { return; } - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); // Technically, we should at this point re-interpret the bytes of the old format to the new. // That might get tricky, and could cause unnecessary slowness in some games. @@ -478,7 +478,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { return false; } - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); return true; } @@ -490,7 +490,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { if (!dst->fbo || !src->fbo || !useBufferedRendering_) { // This can happen if we recently switched from non-buffered. if (useBufferedRendering_) - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); return; } diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index fb071c7a50..158442285c 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -501,7 +501,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" + "Readbacks: %d, uploads: %d\n" "Vertex, Fragment shaders loaded: %i, %i\n", gpuStats.msProcessingDisplayLists * 1000.0f, gpuStats.numDrawCalls, @@ -520,6 +520,7 @@ void GPU_DX9::GetStats(char *buffer, size_t bufsize) { gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, gpuStats.numReadbacks, + gpuStats.numUploads, shaderManagerDX9_->GetNumVertexShaders(), shaderManagerDX9_->GetNumFragmentShaders() ); diff --git a/GPU/Directx9/StencilBufferDX9.cpp b/GPU/Directx9/StencilBufferDX9.cpp index 8d8e91b610..95f7b42718 100644 --- a/GPU/Directx9/StencilBufferDX9.cpp +++ b/GPU/Directx9/StencilBufferDX9.cpp @@ -191,7 +191,7 @@ bool FramebufferManagerDX9::NotifyStencilUpload(u32 addr, int size, bool skipZer u16 h = dstBuffer->renderHeight; if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); } D3DVIEWPORT9 vp{ 0, 0, w, h, 0.0f, 1.0f }; device_->SetViewport(&vp); diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 04731ce8a2..798fec698b 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -425,7 +425,7 @@ void TextureCacheDX9::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFrame LPDIRECT3DTEXTURE9 clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_); Draw::Framebuffer *depalFBO = framebufferManagerDX9_->GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); - draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); shaderManager_->DirtyLastShader(); float xoff = -0.5f / framebuffer->renderWidth; diff --git a/GPU/GLES/FramebufferManagerGLES.cpp b/GPU/GLES/FramebufferManagerGLES.cpp index a0b9b60791..9728ddd855 100644 --- a/GPU/GLES/FramebufferManagerGLES.cpp +++ b/GPU/GLES/FramebufferManagerGLES.cpp @@ -504,9 +504,9 @@ void FramebufferManagerGLES::ReformatFramebufferFrom(VirtualFramebuffer *vfb, GE // to exactly reproduce in 4444 and 8888 formats. if (old == GE_FORMAT_565) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } else { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } RebindFramebuffer(); @@ -640,11 +640,11 @@ void FramebufferManagerGLES::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) // Discard the previous contents of this buffer where possible. if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) { - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); GLenum attachments[3] = { GL_COLOR_ATTACHMENT0, GL_STENCIL_ATTACHMENT, GL_DEPTH_ATTACHMENT }; glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, attachments); } else if (gl_extensions.IsGLES) { - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } CHECK_GL_ERROR_IF_DEBUG(); } @@ -653,7 +653,7 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, if (!dst->fbo || !src->fbo || !useBufferedRendering_) { // This can happen if they recently switched from non-buffered. if (useBufferedRendering_) - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); return; } @@ -708,7 +708,7 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, if (useBlit) { draw_->BlitFramebuffer(src->fbo, srcX1, srcY1, srcX2, srcY2, dst->fbo, dstX1, dstY1, dstX2, dstY2, Draw::FB_COLOR_BIT, Draw::FB_BLIT_NEAREST); } else { - draw_->BindFramebufferAsRenderTarget(dst->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(dst->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); draw_->BindFramebufferAsTexture(src->fbo, 0, Draw::FB_COLOR_BIT, 0); // Make sure our 2D drawing program is ready. Compiles only if not already compiled. @@ -982,7 +982,7 @@ void FramebufferManagerGLES::PackFramebufferSync_(VirtualFramebuffer *vfb, int x if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) { #ifdef USING_GLES2 // GLES3 doesn't support using GL_READ_FRAMEBUFFER here. - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); const GLenum target = GL_FRAMEBUFFER; #else const GLenum target = GL_READ_FRAMEBUFFER; @@ -1046,11 +1046,11 @@ void FramebufferManagerGLES::EndFrame() { continue; } - draw_->BindFramebufferAsRenderTarget(temp.second.fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(temp.second.fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); GLenum attachments[3] = { GL_COLOR_ATTACHMENT0, GL_STENCIL_ATTACHMENT, GL_DEPTH_ATTACHMENT }; glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, attachments); } - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP , Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP , Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } CHECK_GL_ERROR_IF_DEBUG(); } diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index eecabf5bfa..b6f112a0ae 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -203,6 +203,26 @@ GPU_GLES::~GPU_GLES() { #endif } +static constexpr int MakeIntelSimpleVer(int v1, int v2, int v3) { + return (v1 << 16) | (v2 << 8) | v3; +} + +static bool HasIntelDualSrcBug(int versions[4]) { + // Intel uses a confusing set of at least 3 version numbering schemes. This is the one given to OpenGL. + switch (MakeIntelSimpleVer(versions[0], versions[1], versions[2])) { + case MakeIntelSimpleVer(9, 17, 10): + case MakeIntelSimpleVer(9, 18, 10): + return false; + case MakeIntelSimpleVer(10, 18, 10): + return versions[3] < 4061; + case MakeIntelSimpleVer(10, 18, 14): + return versions[3] < 4080; + default: + // Older than above didn't support dual src anyway, newer should have the fix. + return false; + } +} + // Take the raw GL extension and versioning data and turn into feature flags. void GPU_GLES::CheckGPUFeatures() { u32 features = 0; @@ -210,18 +230,22 @@ void GPU_GLES::CheckGPUFeatures() { features |= GPU_SUPPORTS_16BIT_FORMATS; if (gl_extensions.ARB_blend_func_extended || gl_extensions.EXT_blend_func_extended) { - if (gl_extensions.gpuVendor == GPU_VENDOR_INTEL || !gl_extensions.VersionGEThan(3, 0, 0)) { - // Don't use this extension to off on sub 3.0 OpenGL versions as it does not seem reliable - // Also on Intel, see https://github.com/hrydgard/ppsspp/issues/4867 - } else { -#ifdef __ANDROID__ - // This appears to be broken on nVidia Shield TV. - if (gl_extensions.gpuVendor != GPU_VENDOR_NVIDIA) { + if (!gl_extensions.VersionGEThan(3, 0, 0)) { + // Don't use this extension on sub 3.0 OpenGL versions as it does not seem reliable + } else if (gl_extensions.gpuVendor == GPU_VENDOR_INTEL) { + // Also on Intel, see https://github.com/hrydgard/ppsspp/issues/10117 + // TODO: Remove entirely sometime reasonably far in driver years after 2015. + const std::string ver = draw_->GetInfoString(Draw::InfoField::APIVERSION); + int versions[4]{}; + if (sscanf(ver.c_str(), "Build %d.%d.%d.%d", &versions[0], &versions[1], &versions[2], &versions[3]) == 4) { + if (!HasIntelDualSrcBug(versions)) { + features |= GPU_SUPPORTS_DUALSOURCE_BLEND; + } + } else { features |= GPU_SUPPORTS_DUALSOURCE_BLEND; } -#else + } else { features |= GPU_SUPPORTS_DUALSOURCE_BLEND; -#endif } } @@ -702,7 +726,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" + "Readbacks: %d, uploads: %d\n" "Vertex, Fragment, Programs loaded: %i, %i, %i\n", gpuStats.msProcessingDisplayLists * 1000.0f, gpuStats.numDrawCalls, @@ -721,6 +745,7 @@ void GPU_GLES::GetStats(char *buffer, size_t bufsize) { gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, gpuStats.numReadbacks, + gpuStats.numUploads, shaderManagerGL_->GetNumVertexShaders(), shaderManagerGL_->GetNumFragmentShaders(), shaderManagerGL_->GetNumPrograms()); diff --git a/GPU/GLES/StencilBufferGLES.cpp b/GPU/GLES/StencilBufferGLES.cpp index de04a7c207..01dfcf254e 100644 --- a/GPU/GLES/StencilBufferGLES.cpp +++ b/GPU/GLES/StencilBufferGLES.cpp @@ -160,9 +160,9 @@ bool FramebufferManagerGLES::NotifyStencilUpload(u32 addr, int size, bool skipZe Draw::Framebuffer *blitFBO = nullptr; if (useBlit) { blitFBO = GetTempFBO(w, h, Draw::FBO_8888); - draw_->BindFramebufferAsRenderTarget(blitFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(blitFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); } else if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); } glViewport(0, 0, w, h); gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE); diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 278bb66b35..8aff3fa645 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -493,7 +493,7 @@ void TextureCacheGLES::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFram const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); GLuint clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_); Draw::Framebuffer *depalFBO = framebufferManagerGL_->GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); - draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); shaderManager_->DirtyLastShader(); TextureShaderApplier shaderApply(depal, framebuffer->bufferWidth, framebuffer->bufferHeight, framebuffer->renderWidth, framebuffer->renderHeight); diff --git a/GPU/GPU.h b/GPU/GPU.h index 7e10db49aa..08d036560c 100644 --- a/GPU/GPU.h +++ b/GPU/GPU.h @@ -68,6 +68,7 @@ struct GPUStatistics { numFlushes = 0; numTexturesDecoded = 0; numReadbacks = 0; + numUploads = 0; numClears = 0; msProcessingDisplayLists = 0; vertexGPUCycles = 0; @@ -88,6 +89,7 @@ struct GPUStatistics { int numShaderSwitches; int numTexturesDecoded; int numReadbacks; + int numUploads; int numClears; double msProcessingDisplayLists; int vertexGPUCycles; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 485475f0f4..d75625a94a 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -1464,11 +1464,8 @@ void GPUCommon::Execute_Spline(u32 op, u32 diff) { indices = Memory::GetPointerUnchecked(gstate_c.indexAddr); } - if (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) { - DEBUG_LOG_REPORT(G3D, "Spline + morph: %i", (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT); - } - if (vertTypeIsSkinningEnabled(gstate.vertType)) { - DEBUG_LOG_REPORT(G3D, "Spline + skinning: %i", vertTypeGetNumBoneWeights(gstate.vertType)); + if ((gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) || vertTypeIsSkinningEnabled(gstate.vertType)) { + DEBUG_LOG_REPORT(G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", gstate.vertType, (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(gstate.vertType)); } int sp_ucount = op & 0xFF; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index d449c2d357..b490659829 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -213,7 +213,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight) { u1 = 1.0f; } if (!hasImage) { - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); return; } @@ -247,7 +247,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight) { if (GetGPUBackend() == GPUBackend::VULKAN) { std::swap(v0, v1); } - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); Draw::Viewport viewport = { 0.0f, 0.0f, dstwidth, dstheight, 0.0f, 1.0f }; draw_->SetViewports(1, &viewport); draw_->SetScissorRect(0, 0, dstwidth, dstheight); @@ -443,11 +443,8 @@ void SoftGPU::ExecuteOp(u32 op, u32 diff) { indices = Memory::GetPointerUnchecked(gstate_c.indexAddr); } - if (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) { - DEBUG_LOG_REPORT(G3D, "Spline + morph: %i", (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT); - } - if (vertTypeIsSkinningEnabled(gstate.vertType)) { - DEBUG_LOG_REPORT(G3D, "Spline + skinning: %i", vertTypeGetNumBoneWeights(gstate.vertType)); + if ((gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) || vertTypeIsSkinningEnabled(gstate.vertType)) { + DEBUG_LOG_REPORT(G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", gstate.vertType, (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(gstate.vertType)); } int sp_ucount = op & 0xFF; diff --git a/GPU/Vulkan/FramebufferVulkan.cpp b/GPU/Vulkan/FramebufferVulkan.cpp index f174ffeb31..607768d71f 100644 --- a/GPU/Vulkan/FramebufferVulkan.cpp +++ b/GPU/Vulkan/FramebufferVulkan.cpp @@ -394,9 +394,9 @@ void FramebufferManagerVulkan::ReformatFramebufferFrom(VirtualFramebuffer *vfb, // to exactly reproduce in 4444 and 8888 formats. if (old == GE_FORMAT_565) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } else { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } } @@ -467,7 +467,7 @@ bool FramebufferManagerVulkan::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb return false; } - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); return true; } @@ -479,7 +479,7 @@ void FramebufferManagerVulkan::BlitFramebuffer(VirtualFramebuffer *dst, int dstX if (!dst->fbo || !src->fbo || !useBufferedRendering_) { // This can happen if they recently switched from non-buffered. if (useBufferedRendering_) - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); return; } diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 2f3689081a..5bdd2fc086 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -214,12 +214,8 @@ void GPU_Vulkan::CheckGPUFeatures() { } if (vulkan_->GetFeaturesEnabled().dualSrcBlend) { switch (vulkan_->GetPhysicalDeviceProperties().vendorID) { - case VULKAN_VENDOR_NVIDIA: - // Workaround for Shield TV and Shield Tablet driver bug. - if (strcmp(vulkan_->GetPhysicalDeviceProperties().deviceName, "NVIDIA Tegra X1") != 0 && - strcmp(vulkan_->GetPhysicalDeviceProperties().deviceName, "NVIDIA Tegra K1") != 0) - features |= GPU_SUPPORTS_DUALSOURCE_BLEND; - break; + // We thought we had a bug here on nVidia but turns out we accidentally #ifdef-ed out crucial + // code on Android. case VULKAN_VENDOR_INTEL: // Workaround for Intel driver bug. break; @@ -668,7 +664,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" + "Readbacks: %d, uploads: %d\n" "Vertex, Fragment, Pipelines loaded: %i, %i, %i\n" "Pushbuffer space used: UBO %d, Vtx %d, Idx %d\n" "%s\n", @@ -689,6 +685,7 @@ void GPU_Vulkan::GetStats(char *buffer, size_t bufsize) { gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, gpuStats.numReadbacks, + gpuStats.numUploads, shaderManagerVulkan_->GetNumVertexShaders(), shaderManagerVulkan_->GetNumFragmentShaders(), pipelineManager_->GetNumPipelines(), diff --git a/GPU/Vulkan/StencilBufferVulkan.cpp b/GPU/Vulkan/StencilBufferVulkan.cpp index 29e379d9bc..8ee4666583 100644 --- a/GPU/Vulkan/StencilBufferVulkan.cpp +++ b/GPU/Vulkan/StencilBufferVulkan.cpp @@ -123,7 +123,7 @@ bool FramebufferManagerVulkan::NotifyStencilUpload(u32 addr, int size, bool skip } // TODO: Find a nice way to clear alpha here too. - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); return true; } @@ -137,7 +137,7 @@ bool FramebufferManagerVulkan::NotifyStencilUpload(u32 addr, int size, bool skip float v1 = 1.0f; MakePixelTexture(src, dstBuffer->format, dstBuffer->fb_stride, dstBuffer->bufferWidth, dstBuffer->bufferHeight, u1, v1); if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); } else { // something is wrong... } diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index fe33b0945f..9c92d03573 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -346,7 +346,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFr Draw::Framebuffer *depalFBO = framebufferManager_->GetTempFBO( framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); - draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); Vulkan2D::Vertex verts[4] = { { -1, -1, 0.0f, 0, 0 }, diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index e5b6addd63..a1963a06a3 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -60,7 +60,7 @@ void DevMenu::CreatePopupContents(UI::ViewGroup *parent) { I18NCategory *dev = GetI18NCategory("Developer"); I18NCategory *sy = GetI18NCategory("System"); - ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f)); LinearLayout *items = new LinearLayout(ORIENT_VERTICAL); items->Add(new CheckBox(&g_Config.bShowFrameProfiler, dev->T("Frame Profiler"), "")); @@ -366,7 +366,7 @@ void SystemInfoScreen::CreateViews() { deviceSpecs->Add(new InfoItem(si->T("ABI"), GetCompilerABI())); #ifdef _WIN32 if (IsDebuggerPresent()) { - deviceSpecs->Add(new InfoItem(si->T("Debugger Present"), si->T("Yes"))); + deviceSpecs->Add(new InfoItem(si->T("Debugger Present"), di->T("Yes"))); } #endif @@ -400,13 +400,13 @@ void SystemInfoScreen::CreateViews() { #endif #endif if (GetGPUBackend() == GPUBackend::OPENGL) { - deviceSpecs->Add(new InfoItem(si->T("Core Context"), gl_extensions.IsCoreContext ? si->T("Yes") : si->T("No"))); + deviceSpecs->Add(new InfoItem(si->T("Core Context"), gl_extensions.IsCoreContext ? di->T("Active") : di->T("Inactive"))); } deviceSpecs->Add(new ItemHeader(si->T("OS Information"))); deviceSpecs->Add(new InfoItem(si->T("Memory Page Size"), StringFromFormat(si->T("%d bytes"), GetMemoryProtectPageSize()))); - deviceSpecs->Add(new InfoItem(si->T("RW/RX exclusive"), PlatformIsWXExclusive() ? si->T("Yes") : si->T("No"))); + deviceSpecs->Add(new InfoItem(si->T("RW/RX exclusive"), PlatformIsWXExclusive() ? di->T("Active") : di->T("Inactive"))); #ifdef ANDROID - deviceSpecs->Add(new InfoItem(si->T("Sustained perf mode"), System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE) ? si->T("Yes") : si->T("No"))); + deviceSpecs->Add(new InfoItem(si->T("Sustained perf mode"), System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE) ? di->T("Supported") : di->T("Unsupported"))); #endif const char *build = si->T("Release"); @@ -701,46 +701,41 @@ void JitCompareScreen::UpdateDisasm() { I18NCategory *dev = GetI18NCategory("Developer"); - JitBlockCache *blockCache = MIPSComp::jit->GetBlockCache(); + JitBlockCacheDebugInterface *blockCacheDebug = MIPSComp::jit->GetBlockCacheDebugInterface(); char temp[256]; - snprintf(temp, sizeof(temp), "%i/%i", currentBlock_, blockCache->GetNumBlocks()); + snprintf(temp, sizeof(temp), "%i/%i", currentBlock_, blockCacheDebug->GetNumBlocks()); blockName_->SetText(temp); - if (currentBlock_ < 0 || currentBlock_ >= blockCache->GetNumBlocks()) { + if (currentBlock_ < 0 || currentBlock_ >= blockCacheDebug->GetNumBlocks()) { leftDisasm_->Add(new TextView(dev->T("No block"))); rightDisasm_->Add(new TextView(dev->T("No block"))); blockStats_->SetText(""); return; } - JitBlock *block = blockCache->GetBlock(currentBlock_); + JitBlockDebugInfo debugInfo = blockCacheDebug->GetBlockDebugInfo(currentBlock_); - snprintf(temp, sizeof(temp), "%08x", block->originalAddress); + snprintf(temp, sizeof(temp), "%08x", debugInfo.originalAddress); blockAddr_->SetText(temp); // Alright. First generate the MIPS disassembly. // TODO: Need a way to communicate branch continuing. - for (u32 addr = block->originalAddress; addr <= block->originalAddress + block->originalSize * 4; addr += 4) { - char temp[256]; - MIPSDisAsm(Memory::Read_Instruction(addr), addr, temp, true); - std::string mipsDis = temp; - leftDisasm_->Add(new TextView(mipsDis))->SetFocusable(true); + for (auto line : debugInfo.origDisasm) { + leftDisasm_->Add(new TextView(line))->SetFocusable(true); } -#if defined(ARM) - std::vector targetDis = DisassembleArm2(block->normalEntry, block->codeSize); -#elif defined(ARM64) - std::vector targetDis = DisassembleArm64(block->normalEntry, block->codeSize); -#elif defined(_M_IX86) || defined(_M_X64) - std::vector targetDis = DisassembleX86(block->normalEntry, block->codeSize); -#endif -#if defined(ARM) || defined(ARM64) || defined(_M_IX86) || defined(_M_X64) - for (size_t i = 0; i < targetDis.size(); i++) { - rightDisasm_->Add(new TextView(targetDis[i]))->SetFocusable(true); + // TODO : When we have both target and IR, need a third column. + if (debugInfo.targetDisasm.size()) { + for (auto line : debugInfo.targetDisasm) { + rightDisasm_->Add(new TextView(line))->SetFocusable(true); + } + } else { + for (auto line : debugInfo.irDisasm) { + rightDisasm_->Add(new TextView(line))->SetFocusable(true); + } } -#endif int numMips = leftDisasm_->GetNumSubviews(); int numHost = rightDisasm_->GetNumSubviews(); @@ -753,7 +748,7 @@ UI::EventReturn JitCompareScreen::OnAddressChange(UI::EventParams &e) { if (!MIPSComp::jit) { return UI::EVENT_DONE; } - JitBlockCache *blockCache = MIPSComp::jit->GetBlockCache(); + JitBlockCacheDebugInterface *blockCache = MIPSComp::jit->GetBlockCacheDebugInterface(); if (!blockCache) return UI::EVENT_DONE; u32 addr; @@ -773,7 +768,7 @@ UI::EventReturn JitCompareScreen::OnShowStats(UI::EventParams &e) { return UI::EVENT_DONE; } - JitBlockCache *blockCache = MIPSComp::jit->GetBlockCache(); + JitBlockCacheDebugInterface *blockCache = MIPSComp::jit->GetBlockCacheDebugInterface(); BlockCacheStats bcStats; blockCache->ComputeStats(bcStats); NOTICE_LOG(JIT, "Num blocks: %i", bcStats.numBlocks); @@ -790,7 +785,6 @@ UI::EventReturn JitCompareScreen::OnShowStats(UI::EventParams &e) { } ctr++; } - return UI::EVENT_DONE; } @@ -821,7 +815,7 @@ UI::EventReturn JitCompareScreen::OnBlockAddress(UI::EventParams &e) { return UI::EVENT_DONE; } - JitBlockCache *blockCache = MIPSComp::jit->GetBlockCache(); + JitBlockCacheDebugInterface *blockCache = MIPSComp::jit->GetBlockCacheDebugInterface(); if (!blockCache) return UI::EVENT_DONE; @@ -839,7 +833,7 @@ UI::EventReturn JitCompareScreen::OnRandomBlock(UI::EventParams &e) { return UI::EVENT_DONE; } - JitBlockCache *blockCache = MIPSComp::jit->GetBlockCache(); + JitBlockCacheDebugInterface *blockCache = MIPSComp::jit->GetBlockCacheDebugInterface(); if (!blockCache) return UI::EVENT_DONE; @@ -865,7 +859,7 @@ void JitCompareScreen::OnRandomBlock(int flag) { if (!MIPSComp::jit) { return; } - JitBlockCache *blockCache = MIPSComp::jit->GetBlockCache(); + JitBlockCacheDebugInterface *blockCache = MIPSComp::jit->GetBlockCacheDebugInterface(); if (!blockCache) return; @@ -873,21 +867,25 @@ void JitCompareScreen::OnRandomBlock(int flag) { if (numBlocks > 0) { bool anyWanted = false; int tries = 0; - while (!anyWanted && tries < 10000) { + while (!anyWanted && tries < numBlocks) { currentBlock_ = rand() % numBlocks; - const JitBlock *b = blockCache->GetBlock(currentBlock_); - for (u32 addr = b->originalAddress; addr <= b->originalAddress + b->originalSize; addr += 4) { + JitBlockDebugInfo b = blockCache->GetBlockDebugInfo(currentBlock_); + u32 mipsBytes = (u32)b.origDisasm.size() * 4; + for (u32 addr = b.originalAddress; addr <= b.originalAddress + mipsBytes; addr += 4) { MIPSOpcode opcode = Memory::Read_Instruction(addr); if (MIPSGetInfo(opcode) & flag) { char temp[256]; MIPSDisAsm(opcode, addr, temp); - // INFO_LOG(HLE, "Stopping VFPU instruction: %s", temp); + // INFO_LOG(HLE, "Stopping at random instruction: %08x %s", addr, temp); anyWanted = true; break; } } tries++; } + + if (!anyWanted) + currentBlock_ = -1; } UpdateDisasm(); } diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 6f5126b526..8f66d26096 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -231,8 +231,8 @@ void EmuScreen::bootGame(const std::string &filename) { host->NotifyUserMessage(gr->T("DefaultCPUClockRequired", "Warning: This game requires the CPU clock to be set to default."), 15.0f); } - loadingViewColor_->Divert(0xFFFFFFFF, 0.15f); - loadingViewVisible_->Divert(UI::V_VISIBLE, 0.15f); + loadingViewColor_->Divert(0xFFFFFFFF, 0.75f); + loadingViewVisible_->Divert(UI::V_VISIBLE, 0.75f); } void EmuScreen::bootComplete() { @@ -240,8 +240,6 @@ void EmuScreen::bootComplete() { host->BootDone(); host->UpdateDisassembly(); - g_gameInfoCache->FlushBGs(); - NOTICE_LOG(BOOT, "Loading %s...", PSP_CoreParameter().fileToStart.c_str()); autoLoad(); @@ -843,6 +841,37 @@ void EmuScreen::processAxis(const AxisInput &axis, int direction) { } } +class GameInfoBGView : public UI::InertView { +public: + GameInfoBGView(const std::string &gamePath, UI::LayoutParams *layoutParams) : InertView(layoutParams), gamePath_(gamePath) { + } + + void Draw(UIContext &dc) { + // Should only be called when visible. + std::shared_ptr ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, GAMEINFO_WANTBG); + dc.Flush(); + + // PIC1 is the loading image, so let's only draw if it's available. + if (ginfo && ginfo->pic1.texture) { + dc.GetDrawContext()->BindTexture(0, ginfo->pic1.texture->GetTexture()); + + double loadTime = ginfo->pic1.timeLoaded; + uint32_t color = alphaMul(color_, ease((time_now_d() - loadTime) * 3)); + dc.Draw()->DrawTexRect(dc.GetBounds(), 0, 0, 1, 1, color); + dc.Flush(); + dc.RebindTexture(); + } + } + + void SetColor(uint32_t c) { + color_ = c; + } + +protected: + std::string gamePath_; + uint32_t color_ = 0xFFC0C0C0; +}; + void EmuScreen::CreateViews() { using namespace UI; @@ -863,14 +892,31 @@ void EmuScreen::CreateViews() { root_->Add(saveStatePreview_); root_->Add(new OnScreenMessagesView(new AnchorLayoutParams((Size)bounds.w, (Size)bounds.h))); - loadingView_ = new TextView(sc->T("Loading game..."), new AnchorLayoutParams(bounds.centerX(), bounds.centerY(), NONE, NONE, true)); - root_->Add(loadingView_); + GameInfoBGView *loadingBG = root_->Add(new GameInfoBGView(gamePath_, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT))); + TextView *loadingTextView = root_->Add(new TextView(sc->T("Loading game..."), new AnchorLayoutParams(bounds.centerX(), bounds.centerY(), NONE, NONE, true))); + loadingTextView->SetShadow(true); + loadingView_ = loadingTextView; + + loadingViewColor_ = loadingTextView->AddTween(new CallbackColorTween(0x00FFFFFF, 0x00FFFFFF, 0.2f, &bezierEaseInOut)); + loadingViewColor_->SetCallback([loadingBG, loadingTextView](View *v, uint32_t c) { + loadingBG->SetColor(c & 0xFFC0C0C0); + loadingTextView->SetTextColor(c); + }); + loadingViewColor_->Persist(); // We start invisible here, in case of recreated views. - loadingViewColor_ = loadingView_->AddTween(new TextColorTween(0x00FFFFFF, 0x00FFFFFF, 0.2f, &bezierEaseInOut)); - loadingViewColor_->Persist(); - loadingViewVisible_ = loadingView_->AddTween(new VisibilityTween(UI::V_INVISIBLE, UI::V_INVISIBLE, 0.2f, &bezierEaseInOut)); + loadingViewVisible_ = loadingTextView->AddTween(new VisibilityTween(UI::V_INVISIBLE, UI::V_INVISIBLE, 0.2f, &bezierEaseInOut)); loadingViewVisible_->Persist(); + loadingViewVisible_->Finish.Add([loadingBG](EventParams &p) { + loadingBG->SetVisibility(p.v->GetVisibility()); + + // If we just became invisible, flush BGs since we don't need them anymore. + // Saves some VRAM for the game, but don't do it before we fade out... + if (p.v->GetVisibility() == V_INVISIBLE) { + g_gameInfoCache->FlushBGs(); + } + return EVENT_DONE; + }); } UI::EventReturn EmuScreen::OnDevTools(UI::EventParams ¶ms) { @@ -1054,9 +1100,9 @@ void EmuScreen::preRender() { if ((!useBufferedRendering && !g_Config.bSoftwareRendering) || Core_IsStepping()) { // We need to clear here already so that drawing during the frame is done on a clean slate. if (Core_IsStepping() && gpuStats.numFlips != 0) { - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::DONT_CARE }); + draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::DONT_CARE, RPAction::DONT_CARE }); } else { - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }); + draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }); } Viewport viewport; @@ -1089,7 +1135,7 @@ void EmuScreen::render() { // 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 }); + thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }); renderUI(); return; } @@ -1124,12 +1170,12 @@ void EmuScreen::render() { coreState = CORE_RUNNING; } else if (coreState == CORE_STEPPING) { // If we're stepping, it's convenient not to clear the screen. - thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::DONT_CARE }); + thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::DONT_CARE, RPAction::DONT_CARE }); } 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 }); + thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }); } checkPowerDown(); diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index 6cf8384294..1745e8728c 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -99,6 +99,6 @@ private: int saveStateSlot_; UI::View *loadingView_ = nullptr; - UI::TextColorTween *loadingViewColor_ = nullptr; + UI::CallbackColorTween *loadingViewColor_ = nullptr; UI::VisibilityTween *loadingViewVisible_ = nullptr; }; diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index ad5ddd7bd2..83b97f2b72 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -46,6 +46,7 @@ GameInfoCache *g_gameInfoCache; GameInfo::GameInfo() : fileType(IdentifiedFileType::UNKNOWN) { + pending = true; } GameInfo::~GameInfo() { @@ -227,7 +228,7 @@ bool GameInfo::LoadFromPath(const std::string &gamePath) { title = File::GetFilename(filePath_); } - return fileLoader ? fileLoader->Exists() : true; + return true; } std::shared_ptr GameInfo::GetFileLoader() { @@ -300,16 +301,6 @@ std::string GameInfo::GetTitle() { return title; } -bool GameInfo::IsPending() { - std::lock_guard guard(lock); - return pending; -} - -bool GameInfo::IsWorking() { - std::lock_guard guard(lock); - return working; -} - void GameInfo::SetTitle(const std::string &newTitle) { std::lock_guard guard(lock); title = newTitle; @@ -367,13 +358,14 @@ public: void run() override { if (!info_->LoadFromPath(gamePath_)) return; - - { - std::lock_guard lock(info_->lock); - info_->working = true; - info_->fileType = Identify_File(info_->GetFileLoader().get()); + // In case of a remote file, check if it actually exists before locking. + if (!info_->GetFileLoader()->Exists()) { + info_->pending = false; + return; } + info_->working = true; + info_->fileType = Identify_File(info_->GetFileLoader().get()); switch (info_->fileType) { case IdentifiedFileType::PSP_PBP: case IdentifiedFileType::PSP_PBP_DIRECTORY: @@ -392,6 +384,7 @@ public: goto handleELF; } ERROR_LOG(LOADER, "invalid pbp %s\n", pbpLoader->Path().c_str()); + info_->working = false; return; } @@ -556,11 +549,15 @@ handleELF: // TODO: This will currently read in the whole directory tree. Not really necessary for just a // few files. auto fl = info_->GetFileLoader(); - if (!fl) + if (!fl) { + info_->working = false; return; // Happens with UWP currently, TODO... + } BlockDevice *bd = constructBlockDevice(info_->GetFileLoader().get()); - if (!bd) + if (!bd) { + info_->working = false; return; // nothing to do here.. + } ISOFileSystem umd(&handles, bd); // Alright, let's fetch the PARAM.SFO. @@ -639,7 +636,6 @@ handleELF: info_->installDataSize = info_->GetInstallDataSizeInBytes(); } - std::lock_guard lock(info_->lock); info_->pending = false; info_->working = false; // ILOG("Completed writing info for %s", info_->GetTitle().c_str()); @@ -721,7 +717,8 @@ void GameInfoCache::PurgeType(IdentifiedFileType fileType) { gameInfoWQ_->Flush(); restart: for (auto iter = info_.begin(); iter != info_.end(); iter++) { - if (iter->second->fileType == fileType) { + auto &info = iter->second; + if (!info->working && info->fileType == fileType) { info_.erase(iter); goto restart; } @@ -729,7 +726,7 @@ void GameInfoCache::PurgeType(IdentifiedFileType fileType) { } void GameInfoCache::WaitUntilDone(std::shared_ptr &info) { - while (info->IsPending()) { + while (info->pending) { if (gameInfoWQ_->WaitUntilDone(false)) { // A true return means everything finished, so bail out. // This way even if something gets stuck, we won't hang. @@ -769,7 +766,7 @@ std::shared_ptr GameInfoCache::GetInfo(Draw::DrawContext *draw, const info = std::make_shared(); } - if (info->IsWorking()) { + if (info->working) { // Uh oh, it's currently in process. It could mark pending = false with the wrong wantFlags. // Let's wait it out, then queue. // NOTE: This is bad because we're likely on the UI thread.... diff --git a/UI/GameInfoCache.h b/UI/GameInfoCache.h index 0b4201d4a4..ccad4ba683 100644 --- a/UI/GameInfoCache.h +++ b/UI/GameInfoCache.h @@ -108,9 +108,6 @@ public: std::string GetTitle(); void SetTitle(const std::string &newTitle); - bool IsPending(); - bool IsWorking(); - // Hold this when reading or writing from the GameInfo. // Don't need to hold it when just passing around the pointer, // and obviously also not when creating it and holding the only pointer @@ -142,8 +139,8 @@ public: u64 gameSize = 0; u64 saveDataSize = 0; u64 installDataSize = 0; - bool pending = true; - bool working = false; + std::atomic pending{}; + std::atomic working{}; protected: // Note: this can change while loading, use GetTitle(). diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 05ae58a517..740ef7467d 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -123,7 +123,7 @@ void GameScreen::CreateViews() { rightColumnItems->Add(btnDeleteSaveData_)->OnClick.Handle(this, &GameScreen::OnDeleteSaveData); btnDeleteSaveData_->SetVisibility(V_GONE); - if (info && !info->IsPending()) { + if (info && !info->pending) { otherChoices_.clear(); } @@ -252,7 +252,7 @@ void GameScreen::update() { btnSetBackground_->SetVisibility(UI::V_VISIBLE); } } - if (!info->IsPending()) { + if (!info->pending) { // At this point, the above buttons won't become visible. We can show these now. for (UI::Choice *choice : otherChoices_) { choice->SetVisibility(UI::V_VISIBLE); @@ -419,7 +419,7 @@ void SetBackgroundPopupScreen::update() { PopupScreen::update(); std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath_, GAMEINFO_WANTBG | GAMEINFO_WANTBGDATA); - if (status_ == Status::PENDING && info && !info->IsPending()) { + if (status_ == Status::PENDING && info && !info->pending) { GameInfoTex *pic = nullptr; if (info->pic1.dataLoaded && info->pic1.data.size()) { pic = &info->pic1; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index a54d341239..4ecc1ffcfc 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -67,11 +67,6 @@ #include "gfx/gl_common.h" #endif -#ifdef IOS -extern bool iosCanUseJit; -extern bool targetIsJailbroken; -#endif - extern bool VulkanMayBeAvailable(); GameSettingsScreen::GameSettingsScreen(std::string gamePath, std::string gameID, bool editThenRestore) @@ -1262,14 +1257,7 @@ void DeveloperToolsScreen::CreateViews() { Choice *cpuTests = new Choice(dev->T("Run CPU Tests")); list->Add(cpuTests)->OnClick.Handle(this, &DeveloperToolsScreen::OnRunCPUTests); -#ifdef IOS - const std::string testDirectory = g_Config.flash0Directory + "../"; -#else - const std::string testDirectory = g_Config.memStickDirectory; -#endif - if (!File::Exists(testDirectory + "pspautotests/tests/")) { - cpuTests->SetEnabled(false); - } + cpuTests->SetEnabled(TestsAvailable()); #endif list->Add(new CheckBox(&g_Config.bEnableLogging, dev->T("Enable Logging")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLoggingChanged); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 363ac95a7f..254c8151ff 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -762,6 +762,7 @@ void MainScreen::CreateViews() { tabRecentGames->OnHighlight.Handle(this, &MainScreen::OnGameHighlight); } + Button *grantStorageButton = nullptr; if (hasStorageAccess) { ScrollView *scrollAllGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollAllGames->SetTag("MainScreenAllGames"); @@ -819,7 +820,8 @@ void MainScreen::CreateViews() { LinearLayout *buttonHolder = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); buttonHolder->Add(new Spacer(new LinearLayoutParams(1.0f))); - buttonHolder->Add(new Button(mm->T("Give PPSSPP permission to access storage"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)))->OnClick.Handle(this, &MainScreen::OnAllowStorage); + grantStorageButton = new Button(mm->T("Give PPSSPP permission to access storage"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + buttonHolder->Add(grantStorageButton)->OnClick.Handle(this, &MainScreen::OnAllowStorage); buttonHolder->Add(new Spacer(new LinearLayoutParams(1.0f))); leftColumn->Add(new Spacer(new LinearLayoutParams(0.1f))); @@ -881,7 +883,11 @@ void MainScreen::CreateViews() { root_->Add(rightColumn); } - root_->SetDefaultFocusView(tabHolder_); + if (grantStorageButton) { + root_->SetDefaultFocusView(grantStorageButton); + } else if (tabHolder_->GetVisibility() != V_GONE) { + root_->SetDefaultFocusView(tabHolder_); + } I18NCategory *u = GetI18NCategory("Upgrade"); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 1b4c4ad62a..73a75932e8 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -130,11 +130,6 @@ std::string config_filename; bool g_graphicsInited; -#ifdef IOS -bool iosCanUseJit; -bool targetIsJailbroken; -#endif - // Really need to clean this mess of globals up... but instead I add more :P bool g_TakeScreenshot; static bool isOuya; @@ -191,10 +186,7 @@ int Win32Mix(short *buffer, int numSamples, int bits, int rate, int channels) { #endif // globals -#ifndef _WIN32 -static AndroidLogger *logger = 0; -#endif - +static AndroidLogger *logger = nullptr; std::string boot_filename = ""; void NativeHost::InitSound() { @@ -372,8 +364,6 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch LogManager::Init(); #ifndef _WIN32 - logger = new AndroidLogger(); - g_Config.AddSearchPath(user_data_path); g_Config.AddSearchPath(g_Config.memStickDirectory + "PSP/SYSTEM/"); g_Config.SetDefaultPath(g_Config.memStickDirectory + "PSP/SYSTEM/"); @@ -499,9 +489,13 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch logman->SetEnabled(type, true); logman->SetLogLevel(type, logLevel); } -#ifdef __ANDROID__ - logman->AddListener(logger); #endif + +#if defined(__ANDROID__) || (defined(MOBILE_DEVICE) && !defined(_DEBUG)) + // Enable basic logging for any kind of mobile device, since LogManager doesn't. + // The MOBILE_DEVICE/_DEBUG condition matches LogManager.cpp. + logger = new AndroidLogger(); + logman->AddListener(logger); #endif // Allow the lang directory to be overridden for testing purposes (e.g. Android, where it's hard to @@ -1139,6 +1133,9 @@ void NativeShutdown() { net::Shutdown(); + delete logger; + logger = nullptr; + // Previously we did exit() here on Android but that makes it hard to do things like restart on backend change. // I think we handle most globals correctly or correct-enough now. } diff --git a/UI/ProfilerDraw.cpp b/UI/ProfilerDraw.cpp index 2e3f421490..6ffa3383a4 100644 --- a/UI/ProfilerDraw.cpp +++ b/UI/ProfilerDraw.cpp @@ -58,6 +58,7 @@ void DrawProfile(UIContext &ui) { if (g_Config.bShowFrameProfiler) { PROFILE_THIS_SCOPE("timing"); int numCategories = Profiler_GetNumCategories(); + int numThreads = Profiler_GetNumThreads(); int historyLength = Profiler_GetHistoryLength(); ui.SetFontStyle(ui.theme->uiFont); @@ -66,8 +67,11 @@ void DrawProfile(UIContext &ui) { float legendMinVal = lastMaxVal * (1.0f / 120.0f); std::vector history(historyLength); + std::vector slowestThread(historyLength); std::vector catStatus(numCategories); + Profiler_GetSlowestThreads(&slowestThread[0], historyLength); + float rowH = 30.0f; float legendHeight = 0.0f; float legendWidth = 80.0f; @@ -78,7 +82,7 @@ void DrawProfile(UIContext &ui) { continue; } - Profiler_GetHistory(i, &history[0], historyLength); + Profiler_GetSlowestHistory(i, &slowestThread[0], &history[0], historyLength); catStatus[i] = PROFILE_CAT_NOLEGEND; for (int j = 0; j < historyLength; ++j) { if (history[j] > legendMinVal) { @@ -97,6 +101,10 @@ void DrawProfile(UIContext &ui) { } legendWidth += 20.0f; + if (legendHeight > ui.GetBounds().h) { + legendHeight = ui.GetBounds().h; + } + float legendStartY = legendHeight > ui.GetBounds().centerY() ? ui.GetBounds().y2() - legendHeight : ui.GetBounds().centerY(); float legendStartX = ui.GetBounds().x2() - std::min(legendWidth, 200.0f); @@ -183,6 +191,7 @@ void DrawProfile(UIContext &ui) { } } } + Profiler_GetSlowestHistory(i, &slowestThread[0], &history[0], historyLength); for (int n = 0; n < historyLength; n++) { if (total[n] > maxTotal) diff --git a/UI/ReportScreen.cpp b/UI/ReportScreen.cpp index ce2556e5f7..549a86f604 100644 --- a/UI/ReportScreen.cpp +++ b/UI/ReportScreen.cpp @@ -62,11 +62,11 @@ private: RatingChoice::RatingChoice(const char *captionKey, int *value, LayoutParams *layoutParams) : LinearLayout(ORIENT_VERTICAL, layoutParams), value_(value) { - SetSpacing(-8.0f); + SetSpacing(0.0f); I18NCategory *rp = GetI18NCategory("Reporting"); group_ = new LinearLayout(ORIENT_HORIZONTAL); - Add(new InfoItem(rp->T(captionKey), "")); + Add(new TextView(rp->T(captionKey)))->SetShadow(true); Add(group_); group_->SetSpacing(0.0f); @@ -223,7 +223,7 @@ void ReportScreen::CreateViews() { ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL); - leftColumnItems->Add(new TextView(rp->T("FeedbackDesc", "How's the emulation? Let us and the community know!"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); + leftColumnItems->Add(new TextView(rp->T("FeedbackDesc", "How's the emulation? Let us and the community know!"), new LinearLayoutParams(Margins(12, 5, 0, 5))))->SetShadow(true); if (!Reporting::IsEnabled()) { reportingNotice_ = leftColumnItems->Add(new TextView(rp->T("FeedbackDisabled", "Compatibility server reports must be enabled."), new LinearLayoutParams(Margins(12, 5, 0, 5)))); reportingNotice_->SetShadow(true); @@ -258,6 +258,7 @@ void ReportScreen::CreateViews() { leftColumnItems->Add(new CompatRatingChoice("Overall", (int *)&overall_))->SetEnabledPtr(&enableReporting_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); overallDescription_ = leftColumnItems->Add(new TextView("", new LinearLayoutParams(Margins(10, 0)))); + overallDescription_->SetShadow(true); leftColumnItems->Add(new RatingChoice("Graphics", &graphics_))->SetEnabledPtr(&ratingEnabled_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); leftColumnItems->Add(new RatingChoice("Speed", &speed_))->SetEnabledPtr(&ratingEnabled_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); leftColumnItems->Add(new RatingChoice("Gameplay", &gameplay_))->SetEnabledPtr(&ratingEnabled_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 129d4a731a..fecc7de397 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -228,7 +228,8 @@ bool CtrlDisAsmView::getDisasmAddressText(u32 address, char* dest, bool abbrevia } } else { if (showData) { - sprintf(dest, "%08X %08X", address, Memory::Read_Instruction(address, true).encoding); + u32 encoding = Memory::IsValidAddress(address) ? Memory::Read_Instruction(address, true).encoding : 0; + sprintf(dest, "%08X %08X", address, encoding); } else { sprintf(dest, "%08X", address); } diff --git a/android/jni/TestRunner.cpp b/android/jni/TestRunner.cpp index 29ca2b2a0c..c58aa8e9e4 100644 --- a/android/jni/TestRunner.cpp +++ b/android/jni/TestRunner.cpp @@ -25,11 +25,12 @@ #include #include +#include "ppsspp_config.h" #include "base/basictypes.h" #include "base/display.h" #include "base/logging.h" -#include "gfx/gl_common.h" +#include "Common/FileUtil.h" #include "Core/Core.h" #include "Core/System.h" #include "Core/Config.h" @@ -58,21 +59,37 @@ static std::string TrimNewlines(const std::string &s) { return s.substr(0, p + 1); } -void RunTests() -{ +bool TestsAvailable() { +#ifdef IOS + std::string testDirectory = g_Config.flash0Directory + "../"; +#else + std::string testDirectory = g_Config.memStickDirectory; +#endif + // Hack to easily run the tests on Windows from the submodule + if (File::IsDirectory("../pspautotests")) { + testDirectory = "../"; + } + return File::Exists(testDirectory + "pspautotests/tests/"); +} + +bool RunTests() { std::string output; #ifdef IOS - const std::string baseDirectory = g_Config.flash0Directory + "../"; + std::string baseDirectory = g_Config.flash0Directory + "../"; #else - const std::string baseDirectory = g_Config.memStickDirectory; + std::string baseDirectory = g_Config.memStickDirectory; + // Hack to easily run the tests on Windows from the submodule + if (File::IsDirectory("../pspautotests")) { + baseDirectory = "../"; + } #endif CoreParameter coreParam; coreParam.cpuCore = (CPUCore)g_Config.iCpuCore; - coreParam.gpuCore = g_Config.bSoftwareRendering ? GPUCORE_SOFTWARE : GPUCORE_GLES; + coreParam.gpuCore = GPUCORE_NULL; coreParam.enableSound = g_Config.bEnableSound; - coreParam.graphicsContext = PSP_CoreParameter().graphicsContext; + coreParam.graphicsContext = nullptr; coreParam.mountIso = ""; coreParam.mountRoot = baseDirectory + "pspautotests/"; coreParam.startPaused = false; @@ -95,17 +112,20 @@ void RunTests() coreParam.fileToStart = baseDirectory + "pspautotests/tests/" + testName + ".prx"; std::string expectedFile = baseDirectory + "pspautotests/tests/" + testName + ".expected"; - ILOG("Preparing to execute %s", testName); + ILOG("Preparing to execute '%s'", testName); std::string error_string; output = ""; if (!PSP_Init(coreParam, &error_string)) { ELOG("Failed to init unittest %s : %s", testsToRun[i], error_string.c_str()); PSP_CoreParameter().pixelWidth = pixel_xres; PSP_CoreParameter().pixelHeight = pixel_yres; - return; + return false; } + PSP_BeginHostFrame(); + // Run the emu until the test exits + ILOG("Test: Entering runloop."); while (true) { int blockTicks = usToCycles(1000000 / 10); while (coreState == CORE_RUNNING) { @@ -120,6 +140,7 @@ void RunTests() break; } } + PSP_EndHostFrame(); std::ifstream expected(expectedFile.c_str(), std::ios_base::in); if (!expected) { @@ -152,9 +173,9 @@ void RunTests() } PSP_Shutdown(); } - glViewport(0,0,pixel_xres,pixel_yres); PSP_CoreParameter().pixelWidth = pixel_xres; PSP_CoreParameter().pixelHeight = pixel_yres; PSP_CoreParameter().headLess = false; g_Config.sReportHost = savedReportHost; + return true; // Managed to execute the tests. Says nothing about the result. } diff --git a/android/jni/TestRunner.h b/android/jni/TestRunner.h index 50f4546a6f..b7bd7f53c8 100644 --- a/android/jni/TestRunner.h +++ b/android/jni/TestRunner.h @@ -17,4 +17,5 @@ #pragma once -void RunTests(); +bool RunTests(); +bool TestsAvailable(); diff --git a/assets/lang b/assets/lang index 1e3e4a0ba0..350f71780a 160000 --- a/assets/lang +++ b/assets/lang @@ -1 +1 @@ -Subproject commit 1e3e4a0ba0ca8c0a092e027dfb7c1c4778366db5 +Subproject commit 350f71780adaaec0261d68149c093c6e1d86e215 diff --git a/b.sh b/b.sh index a001421eac..c0cc2115ab 100755 --- a/b.sh +++ b/b.sh @@ -39,13 +39,10 @@ do --no-package) echo "Packaging disabled" PACKAGE=0 ;; - --clang) echo "Clang enabled" + --clang) echo "Clang enabled" export CC=/usr/bin/clang export CXX=/usr/bin/clang++ ;; - --*) echo "Bad option: $1" - exit 1 - ;; *) MAKE_OPT="$1 ${MAKE_OPT}" ;; esac diff --git a/ext/native/profiler/profiler.cpp b/ext/native/profiler/profiler.cpp index d48ad7ebbf..bf037a9510 100644 --- a/ext/native/profiler/profiler.cpp +++ b/ext/native/profiler/profiler.cpp @@ -1,18 +1,27 @@ // Ultra-lightweight category profiler with history. -#include -#include +#include +#include #include +#include +#include #include #include "base/logging.h" #include "base/timeutil.h" #include "gfx_es2/draw_buffer.h" +#include "ppsspp_config.h" #include "profiler/profiler.h" -#define MAX_CATEGORIES 32 // Can be any number -#define MAX_DEPTH 16 // Can be any number +#define MAX_CATEGORIES 64 // Can be any number, represents max profiled names. +#define MAX_DEPTH 16 // Can be any number, represents max nesting depth of profiled names. +#if PPSSPP_PLATFORM(IOS) && defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0 +// iOS did not support C++ thread_local before iOS 9 +#define MAX_THREADS 1 // Can be any number, represents concurrent threads calling the profiler. +#else +#define MAX_THREADS 4 // Can be any number, represents concurrent threads calling the profiler. +#endif #define HISTORY_SIZE 128 // Must be power of 2 #ifndef _DEBUG @@ -27,33 +36,63 @@ struct Category { struct CategoryFrame { CategoryFrame() { memset(time_taken, 0, sizeof(time_taken)); + memset(count, 0, sizeof(count)); } float time_taken[MAX_CATEGORIES]; int count[MAX_CATEGORIES]; }; struct Profiler { - int frameCount; int historyPos; - int depth; - int parentCategory[MAX_DEPTH]; - double eventStart[MAX_CATEGORIES]; + int depth[MAX_THREADS]; + int parentCategory[MAX_THREADS][MAX_DEPTH]; + double eventStart[MAX_THREADS][MAX_CATEGORIES]; double curFrameStart; }; static Profiler profiler; static Category categories[MAX_CATEGORIES]; +static std::mutex categoriesLock; +static int threadIdAfterLast = 0; +static std::mutex threadsLock; static CategoryFrame *history; +#if MAX_THREADS > 1 +thread_local int profilerThreadId = -1; +#else +static int profilerThreadId = 0; +#endif void internal_profiler_init() { memset(&profiler, 0, sizeof(profiler)); - for (int i = 0; i < MAX_DEPTH; i++) { - profiler.parentCategory[i] = -1; +#if MAX_THREADS == 1 + threadIdAfterLast = 1; +#endif + for (int i = 0; i < MAX_THREADS; i++) { + for (int j = 0; j < MAX_DEPTH; j++) { + profiler.parentCategory[i][j] = -1; + } } - history = new CategoryFrame[HISTORY_SIZE]; + history = new CategoryFrame[HISTORY_SIZE * MAX_THREADS]; } -int internal_profiler_find_cat(const char *category_name) { +static int internal_profiler_find_thread() { + int thread_id = profilerThreadId; + if (thread_id != -1) { + return thread_id; + } + + std::lock_guard guard(threadsLock); + if (threadIdAfterLast < MAX_THREADS) { + thread_id = threadIdAfterLast++; + profilerThreadId = thread_id; + return thread_id; + } + + // Just keep reusing the last one. + return threadIdAfterLast - 1; +} + +int internal_profiler_find_cat(const char *category_name, bool create_missing) { int i; for (i = 0; i < MAX_CATEGORIES; i++) { const char *catname = categories[i].name; @@ -68,8 +107,11 @@ int internal_profiler_find_cat(const char *category_name) { } } - if (i < MAX_CATEGORIES && category_name) { - categories[i].name = category_name; + if (i < MAX_CATEGORIES && category_name && create_missing) { + std::lock_guard guard(categoriesLock); + int race_check = internal_profiler_find_cat(category_name, false); + if (race_check == -1) + categories[i].name = category_name; return i; } @@ -77,79 +119,86 @@ int internal_profiler_find_cat(const char *category_name) { } // Suspend, also used to prepare for leaving. -static void internal_profiler_suspend(int category, double now) { - double diff = now - profiler.eventStart[category]; - history[profiler.historyPos].time_taken[category] += (float)diff; - profiler.eventStart[category] = 0.0; +static void internal_profiler_suspend(int thread_id, int category, double now) { + double diff = now - profiler.eventStart[thread_id][category]; + history[MAX_THREADS * profiler.historyPos + thread_id].time_taken[category] += (float)diff; + profiler.eventStart[thread_id][category] = 0.0; } // Resume, also used as part of entering. -static void internal_profiler_resume(int category, double now) { - profiler.eventStart[category] = now; +static void internal_profiler_resume(int thread_id, int category, double now) { + profiler.eventStart[thread_id][category] = now; } -int internal_profiler_enter(const char *category_name) { - int category = internal_profiler_find_cat(category_name); +int internal_profiler_enter(const char *category_name, int *out_thread_id) { + int category = internal_profiler_find_cat(category_name, true); + int thread_id = internal_profiler_find_thread(); if (category == -1 || !history) { return category; } - if (profiler.eventStart[category] == 0.0f) { + int &depth = profiler.depth[thread_id]; + if (profiler.eventStart[thread_id][category] == 0.0f) { double now = real_time_now(); - int parent = profiler.parentCategory[profiler.depth]; + int parent = profiler.parentCategory[thread_id][depth]; // Temporarily suspend the parent on entering a child. if (parent != -1) { - internal_profiler_suspend(parent, now); + internal_profiler_suspend(thread_id, parent, now); } - internal_profiler_resume(category, now); + internal_profiler_resume(thread_id, category, now); } else { DLOG("profiler: recursive enter (%i - %s)", category, category_name); } - profiler.depth++; - profiler.parentCategory[profiler.depth] = category; + depth++; + profiler.parentCategory[thread_id][depth] = category; + *out_thread_id = thread_id; return category; } -void internal_profiler_leave(int category) { +void internal_profiler_leave(int thread_id, int category) { if (category == -1 || !history) { return; } + + int &depth = profiler.depth[thread_id]; if (category < 0 || category >= MAX_CATEGORIES) { ELOG("Bad category index %d", category); - profiler.depth--; + depth--; return; } double now = real_time_now(); - profiler.depth--; - if (profiler.depth < 0) { + depth--; + if (depth < 0) { FLOG("Profiler enter/leave mismatch!"); } - int parent = profiler.parentCategory[profiler.depth]; + int parent = profiler.parentCategory[thread_id][depth]; // When there's recursion, we don't suspend or resume. if (parent != category) { - internal_profiler_suspend(category, now); - history[profiler.historyPos].count[category]++; + internal_profiler_suspend(thread_id, category, now); + history[MAX_THREADS * profiler.historyPos + thread_id].count[category]++; if (parent != -1) { // Resume tracking the parent. - internal_profiler_resume(parent, now); + internal_profiler_resume(thread_id, parent, now); } } } void internal_profiler_end_frame() { - if (profiler.depth != 0) { + int thread_id = internal_profiler_find_thread(); + if (profiler.depth[thread_id] != 0) { + // Threads may be off, but they'll fall into another frame. FLOG("Can't be inside a profiler scope at end of frame!"); } profiler.curFrameStart = real_time_now(); profiler.historyPos++; profiler.historyPos &= (HISTORY_SIZE - 1); - memset(&history[profiler.historyPos], 0, sizeof(history[profiler.historyPos])); + memset(&history[MAX_THREADS * profiler.historyPos], 0, sizeof(CategoryFrame) * MAX_THREADS); } const char *Profiler_GetCategoryName(int i) { @@ -168,13 +217,54 @@ int Profiler_GetNumCategories() { return 0; } -void Profiler_GetHistory(int category, float *data, int count) { +int Profiler_GetNumThreads() { + return threadIdAfterLast; +} + +void Profiler_GetSlowestThreads(int *data, int count) { + int numCategories = Profiler_GetNumCategories(); for (int i = 0; i < HISTORY_SIZE; i++) { int x = i - count + profiler.historyPos + 1; while (x < 0) x += HISTORY_SIZE; while (x >= HISTORY_SIZE) x -= HISTORY_SIZE; - data[i] = history[x].time_taken[category]; + + float slowestTime = 0.0f; + data[i] = 0; + for (int thread = 0; thread < threadIdAfterLast; ++thread) { + float sum = 0.0f; + for (int c = 0; c < numCategories; ++c) { + sum += history[MAX_THREADS * x + thread].time_taken[c]; + } + if (sum > slowestTime) { + slowestTime = sum; + data[i] = thread; + } + } + } +} + +void Profiler_GetSlowestHistory(int category, int *slowestThreads, float *data, int count) { + for (int i = 0; i < HISTORY_SIZE; i++) { + int x = i - count + profiler.historyPos + 1; + while (x < 0) + x += HISTORY_SIZE; + while (x >= HISTORY_SIZE) + x -= HISTORY_SIZE; + + int thread = slowestThreads[i]; + data[i] = history[MAX_THREADS * x + thread].time_taken[category]; + } +} + +void Profiler_GetHistory(int category, int thread, float *data, int count) { + for (int i = 0; i < HISTORY_SIZE; i++) { + int x = i - count + profiler.historyPos + 1; + while (x < 0) + x += HISTORY_SIZE; + while (x >= HISTORY_SIZE) + x -= HISTORY_SIZE; + data[i] = history[MAX_THREADS * x + thread].time_taken[category]; } } diff --git a/ext/native/profiler/profiler.h b/ext/native/profiler/profiler.h index 46a276c536..9953b09076 100644 --- a/ext/native/profiler/profiler.h +++ b/ext/native/profiler/profiler.h @@ -2,31 +2,33 @@ #include -// NOTE : This profiler is very single-threaded. Cannot be used from multiple threads yet. - class DrawBuffer; void internal_profiler_init(); void internal_profiler_end_frame(); -int internal_profiler_enter(const char *category_name); // Returns the category number. -void internal_profiler_leave(int category); +int internal_profiler_enter(const char *category_name, int *thread_id); // Returns the category number. +void internal_profiler_leave(int thread_id, int category); const char *Profiler_GetCategoryName(int i); int Profiler_GetNumCategories(); int Profiler_GetHistoryLength(); -void Profiler_GetHistory(int i, float *data, int count); +int Profiler_GetNumThreads(); +void Profiler_GetSlowestThreads(int *data, int count); +void Profiler_GetSlowestHistory(int category, int *slowestThreads, float *data, int count); +void Profiler_GetHistory(int category, int thread, float *data, int count); class ProfileThis { public: ProfileThis(const char *category) { - cat_ = internal_profiler_enter(category); + cat_ = internal_profiler_enter(category, &thread_); } ~ProfileThis() { - internal_profiler_leave(cat_); + internal_profiler_leave(thread_, cat_); } private: int cat_; + int thread_; }; #define PROFILE_INIT() internal_profiler_init(); diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index a2c5c0a631..e693dc834a 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -7,7 +7,6 @@ void VulkanQueueRunner::CreateDeviceObjects() { ILOG("VulkanQueueRunner::CreateDeviceObjects"); InitBackbufferRenderPass(); - InitRenderpasses(); #if 0 // Just to check whether it makes sense to split some of these. drawidx is way bigger than the others... @@ -67,11 +66,12 @@ void VulkanQueueRunner::DestroyDeviceObjects() { vulkan_->Delete().QueueDeleteBuffer(readbackBuffer_); readbackBufferSize_ = 0; - for (int i = 0; i < ARRAY_SIZE(renderPasses_); i++) { - assert(renderPasses_[i] != VK_NULL_HANDLE); - vulkan_->Delete().QueueDeleteRenderPass(renderPasses_[i]); - renderPasses_[i] = VK_NULL_HANDLE; - } + renderPasses_.Iterate([&](const RPKey &rpkey, VkRenderPass rp) { + _assert_(rp != VK_NULL_HANDLE); + vulkan_->Delete().QueueDeleteRenderPass(rp); + }); + renderPasses_.Clear(); + assert(backbufferRenderPass_ != VK_NULL_HANDLE); vulkan_->Delete().QueueDeleteRenderPass(backbufferRenderPass_); backbufferRenderPass_ = VK_NULL_HANDLE; @@ -141,13 +141,28 @@ void VulkanQueueRunner::InitBackbufferRenderPass() { assert(res == VK_SUCCESS); } -void VulkanQueueRunner::InitRenderpasses() { - // Create a bunch of render pass objects, for normal rendering with a depth buffer, - // with clearing, without clearing, and dont-care for both depth/stencil and color, so 3*3=9 combos. +VkRenderPass VulkanQueueRunner::GetRenderPass(VKRRenderPassAction colorLoadAction, VKRRenderPassAction depthLoadAction, VKRRenderPassAction stencilLoadAction) { + RPKey key{ colorLoadAction, depthLoadAction, stencilLoadAction }; + auto pass = renderPasses_.Get(key); + if (pass) { + return pass; + } + VkAttachmentDescription attachments[2] = {}; attachments[0].format = VK_FORMAT_R8G8B8A8_UNORM; attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + switch (colorLoadAction) { + case VKRRenderPassAction::CLEAR: + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + break; + case VKRRenderPassAction::KEEP: + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + break; + case VKRRenderPassAction::DONT_CARE: + default: + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + break; + } attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; @@ -157,9 +172,29 @@ void VulkanQueueRunner::InitRenderpasses() { attachments[1].format = vulkan_->GetDeviceInfo().preferredDepthStencilFormat; attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + switch (depthLoadAction) { + case VKRRenderPassAction::CLEAR: + attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + break; + case VKRRenderPassAction::KEEP: + attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + break; + case VKRRenderPassAction::DONT_CARE: + attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + break; + } + switch (stencilLoadAction) { + case VKRRenderPassAction::CLEAR: + attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + break; + case VKRRenderPassAction::KEEP: + attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + break; + case VKRRenderPassAction::DONT_CARE: + attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + break; + } attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; @@ -192,31 +227,10 @@ void VulkanQueueRunner::InitRenderpasses() { rp.pSubpasses = &subpass; rp.dependencyCount = 0; - for (int depth = 0; depth < 3; depth++) { - switch ((VKRRenderPassAction)depth) { - case VKRRenderPassAction::CLEAR: - attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - break; - case VKRRenderPassAction::KEEP: - attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - break; - case VKRRenderPassAction::DONT_CARE: - attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - break; - } - for (int color = 0; color < 3; color++) { - switch ((VKRRenderPassAction)color) { - case VKRRenderPassAction::CLEAR: attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; break; - case VKRRenderPassAction::KEEP: attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; break; - case VKRRenderPassAction::DONT_CARE: attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; break; - } - int index = RPIndex((VKRRenderPassAction)color, (VKRRenderPassAction)depth); - vkCreateRenderPass(vulkan_->GetDevice(), &rp, nullptr, &renderPasses_[index]); - } - } + VkResult res = vkCreateRenderPass(vulkan_->GetDevice(), &rp, nullptr, &pass); + _assert_(res == VK_SUCCESS); + renderPasses_.Insert(key, pass); + return pass; } void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, const std::vector &steps) { @@ -596,7 +610,7 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step fb->depth.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } - renderPass = renderPasses_[RPIndex(step.render.color, step.render.depthStencil)]; + renderPass = GetRenderPass(step.render.color, step.render.depthStencil, step.render.depthStencil); if (step.render.color == VKRRenderPassAction::CLEAR) { Uint8x4ToFloat4(clearVal[0].color.float32, step.render.clearColor); numClearVals = 1; diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index f2bafcff40..1049a5bb80 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -2,6 +2,7 @@ #include +#include "Common/Hashmaps.h" #include "Common/Vulkan/VulkanContext.h" #include "math/dataconv.h" #include "thin3d/DataFormat.h" @@ -87,7 +88,7 @@ enum class VKRStepType : uint8_t { READBACK_IMAGE, }; -enum class VKRRenderPassAction { +enum class VKRRenderPassAction : uint8_t { DONT_CARE, CLEAR, KEEP, @@ -144,7 +145,7 @@ struct VKRStep { class VulkanQueueRunner { public: - VulkanQueueRunner(VulkanContext *vulkan) : vulkan_(vulkan) {} + VulkanQueueRunner(VulkanContext *vulkan) : vulkan_(vulkan), renderPasses_(16) {} void SetBackbuffer(VkFramebuffer fb, VkImage img) { backbuffer_ = fb; backbufferImage_ = img; @@ -158,9 +159,7 @@ public: VkRenderPass GetBackbufferRenderPass() const { return backbufferRenderPass_; } - VkRenderPass GetRenderPass(int i) const { - return renderPasses_[i]; - } + VkRenderPass GetRenderPass(VKRRenderPassAction colorLoadAction, VKRRenderPassAction depthLoadAction, VKRRenderPassAction stencilLoadAction); inline int RPIndex(VKRRenderPassAction color, VKRRenderPassAction depth) { return (int)depth * 3 + (int)color; @@ -170,7 +169,6 @@ public: private: void InitBackbufferRenderPass(); - void InitRenderpasses(); void PerformBindFramebufferAsRenderTarget(const VKRStep &pass, VkCommandBuffer cmd); void PerformRenderPass(const VKRStep &pass, VkCommandBuffer cmd); @@ -197,9 +195,16 @@ private: VkFramebuffer curFramebuffer_ = VK_NULL_HANDLE; VkRenderPass backbufferRenderPass_ = VK_NULL_HANDLE; + + struct RPKey { + VKRRenderPassAction colorAction; + VKRRenderPassAction depthAction; + VKRRenderPassAction stencilAction; + }; + // Renderpasses, all combinations of preserving or clearing or dont-care-ing fb contents. // TODO: Create these on demand. - VkRenderPass renderPasses_[9]{}; + DenseHashMap renderPasses_; // Readback buffer. Currently we only support synchronous readback, so we only really need one. // We size it generously. diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 724f37070f..f57bf4eab0 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -39,13 +39,20 @@ void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int vkCreateImage(vulkan->GetDevice(), &ici, nullptr, &img.image); - // TODO: If available, use nVidia's VK_NV_dedicated_allocation for framebuffers - VkMemoryRequirements memreq; vkGetImageMemoryRequirements(vulkan->GetDevice(), img.image, &memreq); VkMemoryAllocateInfo alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; alloc.allocationSize = memreq.size; + + // Hint to the driver that this allocation is image-specific. Some drivers benefit. + // We only bother supporting the KHR extension, not the old NV one. + VkMemoryDedicatedAllocateInfoKHR dedicated{ VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR }; + if (vulkan->DeviceExtensions().DEDICATED_ALLOCATION) { + alloc.pNext = &dedicated; + dedicated.image = img.image; + } + vulkan->MemoryTypeFromProperties(memreq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &alloc.memoryTypeIndex); VkResult res = vkAllocateMemory(vulkan->GetDevice(), &alloc, nullptr, &img.memory); assert(res == VK_SUCCESS); diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index c5e13130af..b074e3b4f9 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -194,15 +194,15 @@ public: VkCommandBuffer GetInitCmd(); - VkRenderPass GetRenderPass(int pass) const { - return queueRunner_.GetRenderPass(pass); + VkRenderPass GetRenderPass(VKRRenderPassAction colorLoadAction, VKRRenderPassAction depthLoadAction, VKRRenderPassAction stencilLoadAction) { + return queueRunner_.GetRenderPass(colorLoadAction, depthLoadAction, stencilLoadAction); } - VkRenderPass GetBackbufferRenderPass() const { + VkRenderPass GetBackbufferRenderPass() { return queueRunner_.GetBackbufferRenderPass(); } - VkRenderPass GetCompatibleRenderPass() const { + VkRenderPass GetCompatibleRenderPass() { if (curRenderStep_ && curRenderStep_->render.framebuffer != nullptr) { - return queueRunner_.GetRenderPass(0); + return queueRunner_.GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR); } else { return queueRunner_.GetBackbufferRenderPass(); } diff --git a/ext/native/thin3d/thin3d.h b/ext/native/thin3d/thin3d.h index 916cc97f7e..b401a6268f 100644 --- a/ext/native/thin3d/thin3d.h +++ b/ext/native/thin3d/thin3d.h @@ -532,6 +532,7 @@ enum class RPAction { struct RenderPassInfo { RPAction color; RPAction depth; + RPAction stencil; uint32_t clearColor; float clearDepth; uint8_t clearStencil; diff --git a/ext/native/thin3d/thin3d_d3d11.cpp b/ext/native/thin3d/thin3d_d3d11.cpp index bd42955300..2b2c761279 100644 --- a/ext/native/thin3d/thin3d_d3d11.cpp +++ b/ext/native/thin3d/thin3d_d3d11.cpp @@ -1460,8 +1460,15 @@ void D3D11DrawContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const Ren Uint8x4ToFloat4(cv, rp.clearColor); context_->ClearRenderTargetView(curRenderTargetView_, cv); } - if (rp.depth == RPAction::CLEAR && curDepthStencilView_) { - context_->ClearDepthStencilView(curDepthStencilView_, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, rp.clearDepth, rp.clearStencil); + int mask = 0; + if (rp.depth == RPAction::CLEAR) { + mask |= D3D11_CLEAR_DEPTH; + } + if (rp.stencil == RPAction::CLEAR) { + mask |= D3D11_CLEAR_STENCIL; + } + if (mask && curDepthStencilView_) { + context_->ClearDepthStencilView(curDepthStencilView_, mask, rp.clearDepth, rp.clearStencil); } } diff --git a/ext/native/thin3d/thin3d_d3d9.cpp b/ext/native/thin3d/thin3d_d3d9.cpp index f4c0ec8302..627031991d 100644 --- a/ext/native/thin3d/thin3d_d3d9.cpp +++ b/ext/native/thin3d/thin3d_d3d9.cpp @@ -1079,7 +1079,10 @@ void D3D9Context::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPa clearFlags |= D3DCLEAR_TARGET; } if (rp.depth == RPAction::CLEAR) { - clearFlags |= D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL; + clearFlags |= D3DCLEAR_ZBUFFER; + } + if (rp.stencil == RPAction::CLEAR) { + clearFlags |= D3DCLEAR_STENCIL; } if (clearFlags) { dxstate.scissorTest.force(false); diff --git a/ext/native/thin3d/thin3d_gl.cpp b/ext/native/thin3d/thin3d_gl.cpp index a01810850b..95f9f6532a 100644 --- a/ext/native/thin3d/thin3d_gl.cpp +++ b/ext/native/thin3d/thin3d_gl.cpp @@ -1661,9 +1661,12 @@ void OpenGLContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const Render #else glClearDepth(rp.clearDepth); #endif - glClearStencil(rp.clearStencil); - clearFlags |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + clearFlags |= GL_DEPTH_BUFFER_BIT; glstate.depthWrite.force(GL_TRUE); + } + if (rp.stencil == RPAction::CLEAR) { + glClearStencil(rp.clearStencil); + clearFlags |= GL_STENCIL_BUFFER_BIT; glstate.stencilFunc.force(GL_ALWAYS, 0, 0); glstate.stencilMask.force(0xFF); } @@ -1677,6 +1680,8 @@ void OpenGLContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const Render } if (rp.depth == RPAction::CLEAR) { glstate.depthWrite.restore(); + } + if (rp.stencil == RPAction::CLEAR) { glstate.stencilFunc.restore(); glstate.stencilMask.restore(); } diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index f8906188a7..2dac9803fa 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -462,7 +462,7 @@ public: switch (obj) { case NativeObject::FRAMEBUFFER_RENDERPASS: // Return a representative renderpass. - return (uintptr_t)renderManager_.GetRenderPass(0); + return (uintptr_t)renderManager_.GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR); case NativeObject::BACKBUFFER_RENDERPASS: return (uintptr_t)renderManager_.GetBackbufferRenderPass(); case NativeObject::COMPATIBLE_RENDERPASS: @@ -1295,7 +1295,7 @@ private: Framebuffer *VKContext::CreateFramebuffer(const FramebufferDesc &desc) { VkCommandBuffer cmd = renderManager_.GetInitCmd(); - VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetRenderPass(0), desc.width, desc.height); + VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR), desc.width, desc.height); return new VKFramebuffer(vkrfb); } diff --git a/ext/native/ui/ui_screen.cpp b/ext/native/ui/ui_screen.cpp index 7581e5fcdc..26e12b8b4f 100644 --- a/ext/native/ui/ui_screen.cpp +++ b/ext/native/ui/ui_screen.cpp @@ -33,8 +33,9 @@ void UIScreen::DoRecreateViews() { delete root_; root_ = nullptr; CreateViews(); - if (root_ && root_->GetDefaultFocusView()) { - root_->GetDefaultFocusView()->SetFocus(); + UI::View *defaultView = root_ ? root_->GetDefaultFocusView() : nullptr; + if (defaultView && defaultView->GetVisibility() == UI::V_VISIBLE) { + defaultView->SetFocus(); } recreateViews_ = false; @@ -68,7 +69,7 @@ void UIScreen::preRender() { } draw->BeginFrame(); // Bind and clear the back buffer - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }); + draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }); Draw::Viewport viewport; viewport.TopLeftX = 0; diff --git a/ext/native/ui/view.cpp b/ext/native/ui/view.cpp index 367e7e04a9..0585b62d27 100644 --- a/ext/native/ui/view.cpp +++ b/ext/native/ui/view.cpp @@ -837,7 +837,7 @@ void TextView::Draw(UIContext &dc) { dc.SetFontStyle(small_ ? dc.theme->uiFontSmall : dc.theme->uiFont); if (shadow_) { uint32_t shadowColor = 0x80000000; - dc.DrawTextRect(text_.c_str(), bounds_, shadowColor, textAlign_); + dc.DrawTextRect(text_.c_str(), bounds_.Offset(1.0f, 1.0f), shadowColor, textAlign_); } uint32_t textColor = hasTextColor_ ? textColor_ : dc.theme->infoStyle.fgColor; dc.DrawTextRect(text_.c_str(), bounds_, textColor, textAlign_); diff --git a/ext/native/ui/viewgroup.cpp b/ext/native/ui/viewgroup.cpp index 5a9b4b425b..b6e9c08dcc 100644 --- a/ext/native/ui/viewgroup.cpp +++ b/ext/native/ui/viewgroup.cpp @@ -1555,7 +1555,9 @@ void UpdateViewHierarchy(ViewGroup *root) { std::lock_guard lock(focusLock); EnableFocusMovement(true); if (!GetFocusedView()) { - if (root->GetDefaultFocusView()) { + View *defaultView = root->GetDefaultFocusView(); + // Can't focus what you can't see. + if (defaultView && defaultView->GetVisibility() == V_VISIBLE) { root->GetDefaultFocusView()->SetFocus(); } else { root->SetFocus(); diff --git a/ext/native/ui/viewgroup.h b/ext/native/ui/viewgroup.h index b7de8c539c..6ede1fb9d2 100644 --- a/ext/native/ui/viewgroup.h +++ b/ext/native/ui/viewgroup.h @@ -24,7 +24,7 @@ struct NeighborResult { class ViewGroup : public View { public: - ViewGroup(LayoutParams *layoutParams = 0) : View(layoutParams), defaultFocusView_(0), hasDropShadow_(false), clip_(false) {} + ViewGroup(LayoutParams *layoutParams = 0) : View(layoutParams) {} virtual ~ViewGroup(); // Pass through external events to children. @@ -83,11 +83,11 @@ public: protected: std::mutex modifyLock_; // Hold this when changing the subviews. std::vector views_; - View *defaultFocusView_; + View *defaultFocusView_ = nullptr; Drawable bg_; float dropShadowExpand_ = 0.0f; - bool hasDropShadow_; - bool clip_; + bool hasDropShadow_ = false; + bool clip_ = false; }; // A frame layout contains a single child view (normally). diff --git a/ios/AppDelegate.mm b/ios/AppDelegate.mm index 7926aa5f8e..5ae6e18003 100644 --- a/ios/AppDelegate.mm +++ b/ios/AppDelegate.mm @@ -1,21 +1,108 @@ #import "AppDelegate.h" #import "ViewController.h" #import "base/NativeApp.h" +#import "Core/System.h" +#import "Core/Config.h" +#import "Common/Log.h" + +#import @implementation AppDelegate + +// This will be called when the user receives and dismisses a phone call +// or other interruption to the audio session +// Registered in application:didFinishLaunchingWithOptions: +// for AVAudioSessionInterruptionNotification +-(void) handleAudioSessionInterruption:(NSNotification *)notification { + NSNumber *interruptionType = notification.userInfo[AVAudioSessionInterruptionTypeKey]; + + // Sanity check in case it's somehow not an NSNumber + if (![interruptionType respondsToSelector:@selector(unsignedIntegerValue)]) { + return; // Lets not crash + } + + switch ([interruptionType unsignedIntegerValue]) { + case AVAudioSessionInterruptionTypeBegan: + INFO_LOG(SYSTEM, "ios audio session interruption beginning"); + if (g_Config.bEnableSound) { + Audio_Shutdown(); + } + break; + + case AVAudioSessionInterruptionTypeEnded: + INFO_LOG(SYSTEM, "ios audio session interruption ending"); + if (g_Config.bEnableSound) { + /* + * Only try to reinit audio if in the foreground, otherwise + * it may fail. Instead, trust that applicationDidBecomeActive + * will do it later. + */ + if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { + Audio_Init(); + } + } + break; + + default: + break; + }; +} + +// This will be called when the iOS's shared media process was reset +// Registered in application:didFinishLaunchingWithOptions: +// for AVAudioSessionMediaServicesWereResetNotification +-(void) handleMediaServicesWereReset:(NSNotification *)notification { + INFO_LOG(SYSTEM, "ios media services were reset - reinitializing audio"); + + /* + When media services were reset, Apple recommends: + 1) Dispose of orphaned audio objects (such as players, recorders, + converters, or audio queues) and create new ones + 2) Reset any internal audio states being tracked, including all + properties of AVAudioSession + 3) When appropriate, reactivate the AVAudioSession instance using the + setActive:error: method + We accomplish this by shutting down and reinitializing audio + */ + + if (g_Config.bEnableSound) { + Audio_Shutdown(); + Audio_Init(); + } +} + -(BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.viewController = [[ViewController alloc] init]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMediaServicesWereReset:) name:AVAudioSessionMediaServicesWereResetNotification object:nil]; + self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; + return YES; } +-(void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + -(void) applicationWillResignActive:(UIApplication *)application { + if (g_Config.bEnableSound) { + Audio_Shutdown(); + } + NativeMessageReceived("lost_focus", ""); } -(void) applicationDidBecomeActive:(UIApplication *)application { + if (g_Config.bEnableSound) { + Audio_Init(); + } + NativeMessageReceived("got_focus", ""); } -@end \ No newline at end of file + +@end diff --git a/ios/Launch Screen.storyboard b/ios/Launch Screen.storyboard new file mode 100644 index 0000000000..5b0377f076 --- /dev/null +++ b/ios/Launch Screen.storyboard @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/PPSSPP-Info.plist b/ios/PPSSPP-Info.plist index 6ef7598045..5893f242c8 100644 --- a/ios/PPSSPP-Info.plist +++ b/ios/PPSSPP-Info.plist @@ -58,5 +58,7 @@ 1 2 + UILaunchStoryboardName + Launch Screen diff --git a/ios/PPSSPPUIApplication.h b/ios/PPSSPPUIApplication.h index e7af973f14..cd927c7d01 100644 --- a/ios/PPSSPPUIApplication.h +++ b/ios/PPSSPPUIApplication.h @@ -14,6 +14,9 @@ @interface PPSSPPUIApplication : UIApplication { } + +@property (nonatomic, strong) UISelectionFeedbackGenerator *feedbackGenerator; + @end diff --git a/ios/PPSSPPUIApplication.mm b/ios/PPSSPPUIApplication.mm index c535081b99..d07e7a26e0 100644 --- a/ios/PPSSPPUIApplication.mm +++ b/ios/PPSSPPUIApplication.mm @@ -60,12 +60,6 @@ @implementation PPSSPPUIApplication -- (instancetype)init { - auto instance = [super init]; - instance.delegate = [[AppDelegate alloc] init]; - return instance; -} - - (void)decodeKeyEvent:(NSInteger *)eventMem { NSInteger eventType = eventMem[GSEVENT_TYPE]; NSInteger eventScanCode = eventMem[GSEVENTKEY_KEYCODE]; diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 528287094c..2ec8d7e7a7 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -6,7 +6,6 @@ // #import "ViewController.h" -#import "AudioEngine.h" #import #include @@ -31,10 +30,6 @@ #define IS_IPAD() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) #define IS_IPHONE() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) -#ifndef kCFCoreFoundationVersionNumber_IOS_9_0 -#define kCFCoreFoundationVersionNumber_IOS_9_0 1240.10 -#endif - class IOSDummyGraphicsContext : public DummyGraphicsContext { public: IOSDummyGraphicsContext() { @@ -54,19 +49,14 @@ private: Draw::DrawContext *draw_; }; -float dp_xscale = 1.0f; -float dp_yscale = 1.0f; +static float dp_xscale = 1.0f; +static float dp_yscale = 1.0f; -double lastSelectPress = 0.0f; -double lastStartPress = 0.0f; -bool simulateAnalog = false; +static double lastSelectPress = 0.0f; +static double lastStartPress = 0.0f; +static bool simulateAnalog = false; -extern ScreenManager *screenManager; - -extern bool iosCanUseJit; -extern bool targetIsJailbroken; - -ViewController* sharedViewController; +__unsafe_unretained static ViewController* sharedViewController; static GraphicsContext *graphicsContext; @interface ViewController () @@ -74,11 +64,8 @@ static GraphicsContext *graphicsContext; std::map iCadeToKeyMap; } -@property (nonatomic) EAGLContext* context; -@property (nonatomic) NSString* documentsPath; -@property (nonatomic) NSString* bundlePath; -@property (nonatomic) NSMutableArray* touches; -@property (nonatomic) AudioEngine* audioEngine; +@property (nonatomic, strong) EAGLContext* context; +@property (nonatomic, strong) NSMutableArray* touches; //@property (nonatomic) iCadeReaderView* iCadeView; #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 @property (nonatomic) GCController *gameController __attribute__((weak_import)); @@ -87,39 +74,12 @@ static GraphicsContext *graphicsContext; @end @implementation ViewController --(bool) isArm64 { - size_t size; - cpu_type_t type; - size = sizeof(type); - sysctlbyname("hw.cputype", &type, &size, NULL, 0); - return type == CPU_TYPE_ARM64; -} -(id) init { self = [super init]; if (self) { sharedViewController = self; self.touches = [NSMutableArray array]; - self.documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - self.bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/assets/"]; - - iosCanUseJit = true; - targetIsJailbroken = false; - NSArray *jailPath = [NSArray arrayWithObjects: - @"/Applications/Cydia.app", - @"/private/var/lib/apt", - @"/private/var/stash", - @"/usr/sbin/sshd", - @"/usr/bin/sshd", nil]; - - for (NSString *string in jailPath) { - if ([[NSFileManager defaultManager] fileExistsAtPath:string]) { - // checking device jailbreak status in order to determine which message to show in GameSettingsScreen - targetIsJailbroken = true; - } - } - - NativeInit(0, NULL, [self.documentsPath UTF8String], [self.bundlePath UTF8String], NULL); iCadeToKeyMap[iCadeJoystickUp] = NKCODE_DPAD_UP; iCadeToKeyMap[iCadeJoystickRight] = NKCODE_DPAD_RIGHT; @@ -214,16 +174,6 @@ static GraphicsContext *graphicsContext; #endif } -- (void)viewDidUnload -{ - [super viewDidUnload]; - - if ([EAGLContext currentContext] == self.context) { - [EAGLContext setCurrentContext:nil]; - } - self.context = nil; -} - - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; @@ -231,7 +181,12 @@ static GraphicsContext *graphicsContext; - (void)dealloc { - [self viewDidUnload]; + sharedViewController = nil; + + if ([EAGLContext currentContext] == self.context) { + [EAGLContext setCurrentContext:nil]; + } + self.context = nil; #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 if ([GCController class]) { diff --git a/ios/iCade/iCadeReaderView.h b/ios/iCade/iCadeReaderView.h index 2f39a3dd52..8a6bb586f9 100755 --- a/ios/iCade/iCadeReaderView.h +++ b/ios/iCade/iCadeReaderView.h @@ -50,7 +50,7 @@ @interface iCadeReaderView : UIView { UIView *inputView; iCadeState _iCadeState; - id _delegate; + id __weak _delegate; struct { bool stateChanged:1; @@ -60,7 +60,7 @@ } @property (nonatomic, assign) iCadeState iCadeState; -@property (nonatomic, assign) id delegate; +@property (nonatomic, weak) id delegate; @property (nonatomic, assign) BOOL active; @end diff --git a/ios/iCade/iCadeReaderView.m b/ios/iCade/iCadeReaderView.m index 48a0a59df3..cd0e94fa59 100755 --- a/ios/iCade/iCadeReaderView.m +++ b/ios/iCade/iCadeReaderView.m @@ -49,7 +49,6 @@ static const char *OFF_STATES = "eczqtrfnmpgv"; - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; - [super dealloc]; } - (void)didEnterBackground { diff --git a/ios/iOSCoreAudio.cpp b/ios/iOSCoreAudio.cpp deleted file mode 100644 index 507b1d320e..0000000000 --- a/ios/iOSCoreAudio.cpp +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) 2012- PPSSPP Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official git repository and contact information can be found at -// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. - -// This code implements the emulated audio using CoreAudio for iOS -// Originally written by jtraynham - -#include "iOSCoreAudio.h" -#include - -#define SAMPLE_RATE 44100 - -#define STREAM_MAX_FRAME_COUNT 2048 -static short stream[STREAM_MAX_FRAME_COUNT * 2 * 2]; // frames * sample size * number of channels - -AudioComponentInstance audioInstance = nil; - -int NativeMix(short *audio, int num_samples); - -OSStatus iOSCoreAudioCallback(void *inRefCon, - AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, - UInt32 inBusNumber, - UInt32 inNumberFrames, - AudioBufferList *ioData) -{ - // see if we have any sound to play - UInt32 frames = (inNumberFrames > STREAM_MAX_FRAME_COUNT ? STREAM_MAX_FRAME_COUNT : inNumberFrames); - UInt32 framesReady = NativeMix(stream, frames); - if (framesReady == 0) { - // oops, we don't currently have any sound, so return silence - *ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence; - return noErr; - } - - // grab the output buffer and copy data into it - AudioSampleType *output = (AudioSampleType *)ioData->mBuffers[0].mData; - UInt32 bytesReady = framesReady * sizeof(short) * 2; - memcpy(output, stream, bytesReady); - // make sure and tell it how much audio data is there - ioData->mBuffers[0].mDataByteSize = bytesReady; - - return noErr; -} - -void iOSCoreAudioInit() -{ - if (!audioInstance) { - OSErr err; - - // first, grab the default output - AudioComponentDescription defaultOutputDescription; - defaultOutputDescription.componentType = kAudioUnitType_Output; - defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO; - defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple; - defaultOutputDescription.componentFlags = 0; - defaultOutputDescription.componentFlagsMask = 0; - AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription); - - // create our instance - err = AudioComponentInstanceNew(defaultOutput, &audioInstance); - if (err != noErr) { - audioInstance = nil; - return; - } - - // create our callback so we can give it the audio data - AURenderCallbackStruct input; - input.inputProc = iOSCoreAudioCallback; - input.inputProcRefCon = NULL; - err = AudioUnitSetProperty(audioInstance, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, - 0, - &input, - sizeof(input)); - if (err != noErr) { - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - return; - } - - // setup the audio format we'll be using (stereo pcm) - AudioStreamBasicDescription streamFormat; - memset(&streamFormat, 0, sizeof(streamFormat)); - streamFormat.mSampleRate = SAMPLE_RATE; - streamFormat.mFormatID = kAudioFormatLinearPCM; - streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; - streamFormat.mBitsPerChannel = sizeof(AudioSampleType) * 8; - streamFormat.mChannelsPerFrame = 2; - streamFormat.mFramesPerPacket = 1; - streamFormat.mBytesPerFrame = (streamFormat.mBitsPerChannel / 8) * streamFormat.mChannelsPerFrame; - streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket; - err = AudioUnitSetProperty(audioInstance, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - 0, - &streamFormat, - sizeof(AudioStreamBasicDescription)); - if (err != noErr) { - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - return; - } - - // k, all setup, so init - err = AudioUnitInitialize(audioInstance); - if (err != noErr) { - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - return; - } - - // finally start playback - err = AudioOutputUnitStart(audioInstance); - if (err != noErr) { - AudioUnitUninitialize(audioInstance); - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - return; - } - - // we're good to go - } -} - -void iOSCoreAudioShutdown() -{ - if (audioInstance) { - AudioOutputUnitStop(audioInstance); - AudioUnitUninitialize(audioInstance); - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - } -} diff --git a/ios/iOSCoreAudio.mm b/ios/iOSCoreAudio.mm new file mode 100644 index 0000000000..b4f828f570 --- /dev/null +++ b/ios/iOSCoreAudio.mm @@ -0,0 +1,168 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +// This code implements the emulated audio using CoreAudio for iOS +// Originally written by jtraynham + +#include "iOSCoreAudio.h" + +#include "Common/Log.h" + +#include +#import + + +#define SAMPLE_RATE 44100 + +AudioComponentInstance audioInstance = nil; + +int NativeMix(short *audio, int num_samples); + +OSStatus iOSCoreAudioCallback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + // see if we have any sound to play + short *output = (short *)ioData->mBuffers[0].mData; + UInt32 framesReady = NativeMix(output, inNumberFrames); + + if (framesReady == 0) { + // oops, we don't currently have any sound, so return silence + *ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence; + } + + /* + * You'd think iOS would want to know how many frames were + * actually generated in case it was less than asked for, but + * apparently that causes micro-stuttering and everything just + * works better if we lie and say we successfully generated as + * many frames as it wanted... weird. We still get micro-stuttering + * but it's less noticeable this way. + */ + //UInt32 bytesReady = framesReady * sizeof(short) * 2; + UInt32 bytesReady = inNumberFrames * sizeof(short) * 2; + ioData->mBuffers[0].mDataByteSize = bytesReady; + + return noErr; +} + +void iOSCoreAudioInit() +{ + NSError *error = nil; + AVAudioSession *session = [AVAudioSession sharedInstance]; + if (![session setActive:YES error:&error]) { + ERROR_LOG(SYSTEM, "Failed to activate AVFoundation audio session"); + if (error.localizedDescription) { + NSLog(@"%@", error.localizedDescription); + } + if (error.localizedFailureReason) { + NSLog(@"%@", error.localizedFailureReason); + } + } + + if (!audioInstance) { + OSErr err; + + // first, grab the default output + AudioComponentDescription defaultOutputDescription; + defaultOutputDescription.componentType = kAudioUnitType_Output; + defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO; + defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + defaultOutputDescription.componentFlags = 0; + defaultOutputDescription.componentFlagsMask = 0; + AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription); + + // create our instance + err = AudioComponentInstanceNew(defaultOutput, &audioInstance); + if (err != noErr) { + audioInstance = nil; + return; + } + + // create our callback so we can give it the audio data + AURenderCallbackStruct input; + input.inputProc = iOSCoreAudioCallback; + input.inputProcRefCon = NULL; + err = AudioUnitSetProperty(audioInstance, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &input, + sizeof(input)); + if (err != noErr) { + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + return; + } + + // setup the audio format we'll be using (stereo pcm) + AudioStreamBasicDescription streamFormat; + memset(&streamFormat, 0, sizeof(streamFormat)); + streamFormat.mSampleRate = SAMPLE_RATE; + streamFormat.mFormatID = kAudioFormatLinearPCM; + streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + streamFormat.mBitsPerChannel = sizeof(short) * 8; + streamFormat.mChannelsPerFrame = 2; + streamFormat.mFramesPerPacket = 1; + streamFormat.mBytesPerFrame = (streamFormat.mBitsPerChannel / 8) * streamFormat.mChannelsPerFrame; + streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket; + err = AudioUnitSetProperty(audioInstance, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &streamFormat, + sizeof(AudioStreamBasicDescription)); + if (err != noErr) { + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + return; + } + + // k, all setup, so init + err = AudioUnitInitialize(audioInstance); + if (err != noErr) { + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + return; + } + + // finally start playback + err = AudioOutputUnitStart(audioInstance); + if (err != noErr) { + AudioUnitUninitialize(audioInstance); + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + return; + } + + // we're good to go + } +} + +void iOSCoreAudioShutdown() +{ + if (audioInstance) { + AudioOutputUnitStop(audioInstance); + AudioUnitUninitialize(audioInstance); + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + } +} + diff --git a/ios/main.mm b/ios/main.mm index 3070e772d6..8a5817982a 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -11,6 +11,7 @@ #import "PPSSPPUIApplication.h" #include "base/NativeApp.h" +#include "profiler/profiler.h" @interface UIApplication (Private) -(void) suspend; @@ -97,23 +98,60 @@ PermissionStatus System_GetPermissionStatus(SystemPermission permission) { retur FOUNDATION_EXTERN void AudioServicesPlaySystemSoundWithVibration(unsigned long, objc_object*, NSDictionary*); -void Vibrate(int length_ms) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - NSArray *pattern = @[@YES, @30, @NO, @2]; - - dictionary[@"VibePattern"] = pattern; - dictionary[@"Intensity"] = @2; - - AudioServicesPlaySystemSoundWithVibration(kSystemSoundID_Vibrate, nil, dictionary); - // TODO: Actually make use of length_ms if PPSSPP ever adds that in the config +BOOL SupportsTaptic() +{ + // we're on an iOS version that cannot instantiate UISelectionFeedbackGenerator, so no. + if(!NSClassFromString(@"UISelectionFeedbackGenerator")) + { + return NO; + } + + // http://www.mikitamanko.com/blog/2017/01/29/haptic-feedback-with-uifeedbackgenerator/ + // use private API against UIDevice to determine the haptic stepping + // 2 - iPhone 7 or above, full taptic feedback + // 1 - iPhone 6S, limited taptic feedback + // 0 - iPhone 6 or below, no taptic feedback + NSNumber* val = (NSNumber*)[[UIDevice currentDevice] valueForKey:@"feedbackSupportLevel"]; + return [val intValue] >= 2; +} + +void Vibrate(int mode) { + + if(SupportsTaptic()) + { + PPSSPPUIApplication* app = (PPSSPPUIApplication*)[UIApplication sharedApplication]; + if(app.feedbackGenerator == nil) + { + app.feedbackGenerator = [[UISelectionFeedbackGenerator alloc] init]; + [app.feedbackGenerator prepare]; + } + [app.feedbackGenerator selectionChanged]; + } + else + { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + NSArray *pattern = @[@YES, @30, @NO, @2]; + + dictionary[@"VibePattern"] = pattern; + dictionary[@"Intensity"] = @2; + + AudioServicesPlaySystemSoundWithVibration(kSystemSoundID_Vibrate, nil, dictionary); + } } int main(int argc, char *argv[]) { // Simulates a debugger. Makes it possible to use JIT (though only W^X) syscall(SYS_ptrace, 0 /*PTRACE_TRACEME*/, 0, 0, 0); + + PROFILE_INIT(); + @autoreleasepool { - return UIApplicationMain(argc, argv, NSStringFromClass([PPSSPPUIApplication class]), nil); - //return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/assets/"]; + + NativeInit(argc, (const char**)argv, documentsPath.UTF8String, bundlePath.UTF8String, NULL); + + return UIApplicationMain(argc, argv, NSStringFromClass([PPSSPPUIApplication class]), NSStringFromClass([AppDelegate class])); } } diff --git a/ppsspp.iss b/ppsspp.iss index f1b3c5bd72..9d37d59206 100644 --- a/ppsspp.iss +++ b/ppsspp.iss @@ -1,21 +1,73 @@ +#define ApplicationName 'PPSSPP' +#define ApplicationVersion GetFileVersion('PPSSPPWindows.exe') + +[Languages] +Name: "en"; MessagesFile: "compiler:Default.isl" +Name: "ar"; MessagesFile: "compiler:Languages\Armenian.islu" +Name: "br"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" +Name: "ca"; MessagesFile: "compiler:Languages\Catalan.isl" +Name: "co"; MessagesFile: "compiler:Languages\Corsican.isl" +Name: "cz"; MessagesFile: "compiler:Languages\Czech.isl" +Name: "da"; MessagesFile: "compiler:Languages\Danish.isl" +Name: "du"; MessagesFile: "compiler:Languages\Dutch.isl" +Name: "fi"; MessagesFile: "compiler:Languages\Finnish.isl" +Name: "fr"; MessagesFile: "compiler:Languages\French.isl" +Name: "ge"; MessagesFile: "compiler:Languages\German.isl" +Name: "gr"; MessagesFile: "compiler:Languages\Greek.isl" +Name: "he"; MessagesFile: "compiler:Languages\Hebrew.isl" +Name: "hu"; MessagesFile: "compiler:Languages\Hungarian.isl" +Name: "it"; MessagesFile: "compiler:Languages\Italian.isl" +Name: "ja"; MessagesFile: "compiler:Languages\Japanese.isl" +Name: "ne"; MessagesFile: "compiler:Languages\Nepali.islu" +Name: "no"; MessagesFile: "compiler:Languages\Norwegian.isl" +Name: "pl"; MessagesFile: "compiler:Languages\Polish.isl" +Name: "po"; MessagesFile: "compiler:Languages\Portuguese.isl" +Name: "ru"; MessagesFile: "compiler:Languages\Russian.isl" +Name: "sc"; MessagesFile: "compiler:Languages\ScottishGaelic.isl" +Name: "sec"; MessagesFile: "compiler:Languages\SerbianCyrillic.isl" +Name: "ser"; MessagesFile: "compiler:Languages\SerbianLatin.isl" +Name: "sl"; MessagesFile: "compiler:Languages\Slovenian.isl" +Name: "sp"; MessagesFile: "compiler:Languages\Spanish.isl" +Name: "tr"; MessagesFile: "compiler:Languages\Turkish.isl" +Name: "uk"; MessagesFile: "compiler:Languages\Ukrainian.isl" +; more languages are available at: http://www.jrsoftware.org/files/istrans/ +; but those need to be mirrored on PPSSPP repo somehow + [Setup] +; Installer - name for title and wizard pages +AppName={#ApplicationName} +; Installer - version +VersionInfoVersion={#ApplicationVersion} + +; Programs and Features - Name +AppVerName={#ApplicationName} +; Programs and Features - Version AppVersion={#ApplicationVersion} -AppName={cm:AppName} +; Programs and Features - Publisher +AppPublisher=PPSSPP Team +; Programs and Features - Help link +AppSupportURL=https://forums.ppsspp.org +; Programs and Features - Support link +AppPublisherURL=https://www.ppsspp.org +; Programs and Features - Update information +AppUpdatesURL=https://www.ppsspp.org/downloads.html + DefaultDirName={pf}\PPSSPP ; Since no icons will be created in "{group}", we don't need the wizard ; to ask for a Start Menu folder name: DisableProgramGroupPage=yes UninstallDisplayIcon={app}\PPSSPPWindows.exe -OutputDir=. -UsePreviousLanguage=no +; Detect the correct directory for Program Files x86/64 +ArchitecturesInstallIn64BitMode=x64 -[CustomMessages] -AppName={#ApplicationName} -LaunchProgram=Start {#ApplicationName} after finishing installation +; Save the installer as ... +OutputBaseFilename={#ApplicationName}_{#ApplicationVersion}_Setup +; ... in the same directory +OutputDir=. [Files] Source: "PPSSPPWindows.exe"; DestDir: "{app}" -Source: "PPSSPPWindows64.exe"; DestDir: "{app}" +Source: "PPSSPPWindows64.exe"; DestDir: "{app}"; Check: IsWin64 Source: "README.md"; DestName: "README.txt"; DestDir: "{app}"; Flags: isreadme ; Special file to signify that we are installed, and not "portable" and should look for ; configuration etc in the user's directory, not in our own subdirectory. @@ -28,7 +80,10 @@ Source: "dx9sdk\8.1\Redist\D3D\x64\d3dcompiler_47.dll"; DestDir: "{app}" Source: "dx9sdk\8.1\Redist\D3D\x86\d3dcompiler_47.dll"; DestName: "d3dcompiler_47.x86.dll"; DestDir: "{app}" [Run] -Filename: {app}\PPSSPPWindows.exe; Description: {cm:LaunchProgram,{cm:AppName}}; Flags: nowait postinstall skipifsilent +Filename: {app}\PPSSPPWindows.exe; Description: {cm:LaunchProgram,{#ApplicationName}}; Flags: nowait postinstall skipifsilent; Check: not IsWin64 +Filename: {app}\PPSSPPWindows64.exe; Description: {cm:LaunchProgram,{#ApplicationName}}; Flags: nowait postinstall skipifsilent; Check: IsWin64 [Icons] -Name: "{commonprograms}\PPSSPP"; Filename: "{app}\PPSSPPWindows.exe" +Name: "{commonprograms}\PPSSPP"; Filename: "{app}\PPSSPPWindows.exe"; Check: not IsWin64 +Name: "{commonprograms}\PPSSPP"; Filename: "{app}\PPSSPPWindows64.exe"; Check: IsWin64 +